-
Notifications
You must be signed in to change notification settings - Fork 0
Feature/licensing architecture #30
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 6 commits
Commits
Show all changes
12 commits
Select commit
Hold shift + click to select a range
8fc4628
before any license work
magmacomputing 74817ac
feat: add Cipher export and implement detailed architecture for JWT-b…
magmacomputing 29694b1
1st draft of Licensing support
magmacomputing 7824c43
PR 1st review
magmacomputing 775fdd4
PR 2nd review
magmacomputing b9c760e
CHANGELOG
magmacomputing 6b753e5
swap over community version
magmacomputing ed73329
wrap-up v2.10.0
magmacomputing c1cb2be
re-wire the license
magmacomputing ec868a0
PR 2nd review
magmacomputing 064f036
PR 3rd review
magmacomputing 01a21ef
clarify License boot order
magmacomputing File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| # Authentication for npmjs | ||
| # Set this in your environment as NPM_TOKEN for CI/CD | ||
| //registry.npmjs.org/:_authToken=${NPM_TOKEN} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| MIT License | ||
|
|
||
| Copyright (c) 2026 Magma Computing | ||
|
|
||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| of this software and associated documentation files (the "Software"), to deal | ||
| in the Software without restriction, including without limitation the rights | ||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| copies of the Software, and to permit persons to whom the Software is | ||
| furnished to do so, subject to the following conditions: | ||
|
|
||
| The above copyright notice and this permission notice shall be included in all | ||
| copies or substantial portions of the Software. | ||
|
|
||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| SOFTWARE. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
| "composite": true, | ||
| "lib": [ | ||
| "ESNext", | ||
| "ESNext.Temporal", | ||
| "DOM" | ||
| ], | ||
| "types": [ | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,48 +1,81 @@ | ||
| # Tempo Licensing Architecture (v2.9.4 Proposal) | ||
| # Tempo Licensing Architecture & Plugin Strategy | ||
|
|
||
| ## Objective | ||
| Enable a robust, secure, and flexible licensing mechanism for premium Tempo plugins without bloating the core engine or compromising user security. | ||
| Implement a secure, user-friendly licensing system for Tempo plugins that enables monetization and IP protection without creating high friction for developers. | ||
|
|
||
| ## Architectural Decisions | ||
| ## The "No-PAT" Distribution Model | ||
| To eliminate developer friction (configuring registries/PATs), all plugins are distributed via the public npmjs registry. | ||
|
|
||
| ### 1. Separation of Concerns | ||
| - **Core Tempo**: Acts only as a "Parking Spot" for the license string. It does NOT contain logic for decoding JWTs or verifying signatures. | ||
| - **Plugins**: House all enforcement logic. The plugin's `install()` method is responsible for fetching the license from the state and validating it. | ||
| - **Storage**: Private source code resides in Magma Computing's GitHub organization. | ||
| - **Distribution**: Plugins are built, minified, and obfuscated before being published as public packages under the `@magmacomputing` scope (e.g., `@magmacomputing/plugin-term`). | ||
| - **Access**: Anyone can `npm install`, but functionality is gated by the "Tempo Activation Key" (JWT). | ||
| - **Free Tier**: The "Astrological Sign" plugin serves as a free reference implementation for users to test the activation and support model. | ||
|
|
||
| ### 2. Internal State Branching | ||
| To prevent sensitive license keys from appearing in diagnostic logs: | ||
| - **Location**: The license will reside in a separate branch of the internal state (e.g., `state.auth` or directly on `state.license`). | ||
| - **Isolation**: It must NOT be part of the `config` or `parse` objects, which are frequently passed to `Logify` for debugging. | ||
| ## Activation & Validation | ||
|
|
||
| ### 3. Cascade of License Discovery | ||
| The engine will look for a license key in the following order: | ||
| ### 1. The Tempo Activation Key (JWT) | ||
| The key is a JSON Web Token issued to the customer. It supports three tiers of usage: | ||
|
|
||
| | Claim | Standard/Trial | Enterprise/Distribution | | ||
| | :--- | :--- | :--- | | ||
| | `aud` (Audience) | Array of domains (e.g. `["localhost", "site.com"]`) | `"*"` (Wildcard - works on any domain) | | ||
| | `exp` (Expiry) | Unix timestamp (e.g. 1 year from issue) | `0` (Perpetual - never expires) | | ||
| | `jti` | Unique ID for revocation | Unique ID for revocation | | ||
|
|
||
| ### 2. Cascade of License Discovery | ||
| Plugins will look for the activation key in the following order: | ||
| 1. **Explicit**: `Tempo.init({ license: '...' })` | ||
| 2. **Discovery**: `globalThis[TEMPO_DISCOVERY].license` | ||
| 2. **Discovery**: `globalThis.__TEMPO_DISCOVERY__.license` | ||
| 3. **Environment**: `process.env.TEMPO_LICENSE` (Server-side) | ||
| 4. **Storage**: `localStorage.getItem('tempo_license')` (Client-side) | ||
| 4. **Configuration**: `.temporc` or `tempo.config.json` (CWD) | ||
| 5. **Storage**: `localStorage.getItem('tempo_license')` (Client-side) | ||
|
|
||
| ### 3. Verification Logic | ||
| - **Domain Locking**: The plugin verifies `window.location.hostname` against the `aud` claim (unless `aud` is `*`). | ||
| - **Grace Period**: If a key is expired, the plugin enters a 7-day "Grace Period," allowing full functionality while emitting a `console.warn`. | ||
| - **Revocation**: | ||
| - Plugins fetch a **Signed Revocation List** (JWS) from Magma's servers every **7 days**. | ||
| - The fetch is shared across all Tempo plugins to minimize overhead. | ||
| - If offline, the plugin "Fails Open" and relies on the JWT's internal expiry. | ||
| - The list is self-cleaning; keys are removed once they naturally expire. | ||
|
|
||
| ### 4. The License "Wallet" (Discovery & Persistence) | ||
| To prevent "Token Fatigue" and the need to re-supply keys, Tempo and its plugins treat the environment as a persistent wallet. | ||
|
|
||
| ## Security & Risks | ||
| - **XSS**: Keys in `localStorage` are vulnerable; documentation must warn users to prefer secure server-side injection where possible. | ||
| - **Leakage**: Even with separate branching, we must ensure internal state dumps (for support) redact this branch by default. | ||
| - **One Key, Many Scopes**: A single JWT can contain multiple plugin identifiers in its `scope` claim (e.g., `scope: ["term", "ticker"]`). | ||
| - **Global Discovery (Browser)**: Developers can define `window.__TEMPO_DISCOVERY__ = { license: '...' }` at the very top of their HTML. Any Tempo instance or plugin will automatically "hydrate" from this global wallet. | ||
| - **Internal Stashing**: Once a license is discovered or provided via `init()`, Tempo core caches it in its internal static state. This ensures that a plugin imported in a different module (or a secondary Tempo instance) can still find the active license without being explicitly passed a key. | ||
| - **Cross-Session Persistence (Opt-in & Unsafe)**: By default, Tempo avoids `localStorage` due to XSS token-theft risks. Persistence is **opt-in** via the `enableCrossSessionPersistence` flag. | ||
| - **Recommended**: Use memory or `sessionStorage` for sensitive keys. | ||
| - **Mitigation Checklist**: | ||
| - Implement strict Content Security Policy (CSP). | ||
| - Use HttpOnly cookies for token delivery where possible. | ||
| - Ensure rigorous input sanitization. | ||
| - Use short TTLs and rotate/revoke tokens frequently. | ||
|
|
||
| ## Remote Invalidation & Revocation | ||
| ### 5. Revocation Infrastructure (The Control Tower) | ||
| To maintain security without manual intervention on the customer side: | ||
|
|
||
| Since Tempo is designed for offline-first stability, we avoid a "mandatory phone-home" on every instance. Instead, we propose the following strategies for invalidating compromised licenses: | ||
| - **Host**: `api.magmacomputing.com.au` | ||
| - **Endpoint**: `/tempo/v1/revoked.jws` (Versioned for future-proofing). | ||
| - **Format**: The list is a Signed JWS. Plugins verify it using an embedded Public Key. | ||
| - **Management**: | ||
| - A local `registry/` folder in the private `tempo-plugin` repo tracks the revocation state. | ||
| - CLI tools (`npm run license:revoke`) handle the signing and deployment of the updated JWS to the production host. | ||
| - **Security**: The **Private Key** for signing is stored as a GH Secret and never committed. The **Public Key** is baked into the obfuscated plugin source. | ||
| - **Fail-Safe**: If the endpoint is unreachable, plugins "Fail Open" and rely on the internal JWT expiry to ensure no service disruption for legitimate users. | ||
|
|
||
| ### 1. Short-lived JWTs (Rotation) | ||
| - Issue licenses with 30- or 90-day expiry. | ||
| - Use a lightweight refresh mechanism to update the license during application build or bootstrap. | ||
| - **Benefit**: Naturally limits the window of exposure for any single leaked key. | ||
| ## Implementation Roadmap | ||
|
|
||
| ### 2. Revocation Lists (Blacklisting) | ||
| - Plugins can periodically fetch a `revoked.json` list of blacklisted JWT IDs (`jti`). | ||
| - If a breach is detected, the `jti` is added to the list, and the plugin disables itself upon the next update/fetch. | ||
| ### Phase 1: Support Infrastructure | ||
| - [ ] Add `license` to `Internal.State` and `BaseOptions` in Tempo Core. | ||
| - [ ] Implement the Discovery Cascade and Shared Revocation Service. | ||
| - [ ] Ensure `Logify` and internal state dumps redact the `state.license` branch. | ||
|
|
||
| ### 3. Graceful Fallback | ||
| - If a license is invalid or expired, the plugin should not crash the application. | ||
| - **Behavior**: Downgrade to "Limited" mode, log a warning, but ensure the core Tempo engine continues to function. | ||
| ### Phase 2: Reference Plugin (Astro) | ||
| - [ ] Create the `@magmacomputing/plugin-astro` workspace. | ||
| - [ ] Implement JWS signature verification and JWT decoding. | ||
| - [ ] Set up the obfuscated build pipeline for public npmjs distribution. | ||
|
|
||
| ## Next Steps (v2.9.4) | ||
| - Add `license` to `Internal.State` and `BaseOptions`. | ||
| - Update `support.init.ts` to implement the Discovery Cascade. | ||
| - Provide a reference implementation in the `tempo-plugin` mono-repo showing how to decode a JWT using Tempo's native date utilities. | ||
| ### Phase 3: Commercial Plugins | ||
| - [ ] Roll out `term`, `ticker`, and other premium extensions using the proven Astro model. |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.