@termix-ssh/plugin-sdk 1.0.13 → 1.0.15
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/CHANGELOG.md +26 -0
- package/PLUGIN-GUIDE.md +16 -1
- package/dist/backend.d.ts +130 -1
- package/dist/backend.js +7 -0
- package/dist/capabilities.js +2 -0
- package/dist/host/{PluginsTab-OXQQ7M4I.js → PluginsTab-S33WIIGC.js} +3 -3
- package/dist/host/{chunk-ERGN4YW6.js → chunk-2IV3VX7F.js} +1 -1
- package/dist/host/{chunk-CN5UBTBP.js → chunk-3L45NNT5.js} +31 -2
- package/dist/host/{chunk-T2PZWXFJ.js → chunk-57DJU2ZA.js} +1 -1
- package/dist/host/{chunk-CALNJCHN.js → chunk-V44FCJBQ.js} +25 -4
- package/dist/host/{chunk-54KCK6IZ.js → chunk-VFTB2RDP.js} +4 -2
- package/dist/host/{linked-server-5VFO4XQB.js → linked-server-SCJH7DSL.js} +1 -1
- package/dist/host/{rbac-api-5YMBJAO5.js → rbac-api-KA5IPQQL.js} +1 -1
- package/dist/host/testing-host.js +4 -4
- package/dist/host/types/src/types/index.d.ts +10 -0
- package/dist/host/types/src/types/ui-types.d.ts +3 -1
- package/dist/host/types/src/ui/api/auth-methods-api.d.ts +2 -0
- package/dist/host/types/src/ui/api/settings-api.d.ts +6 -0
- package/dist/host/types/src/ui/api/ssh-host-management-api.d.ts +6 -0
- package/dist/host/types/src/ui/main-axios.d.ts +2 -2
- package/dist/host/types/src/ui/plugin-host/sdk-ui.d.ts +2 -2
- package/dist/host/types/src/ui/sidebar/AuthEnrollmentSections.d.ts +4 -1
- package/dist/host/ui.js +27 -10
- package/dist/host/{use-permissions-ISEDRY3J.js → use-permissions-LLN6TLAL.js} +1 -1
- package/dist/manifest.d.ts +1 -1
- package/dist/manifest.js +1 -1
- package/dist/testing.d.ts +21 -1
- package/dist/testing.js +61 -1
- package/dist/vitest-preset.js +23 -0
- package/package.json +1 -1
- package/schema/manifest.schema.json +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.15
|
|
4
|
+
|
|
5
|
+
Plugin API 1.7.0.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `ctx.auth.policy.provide` and the `auth:policy` capability, so a plugin can set an idle timeout, cap session length, turn off remember me and require a second factor
|
|
10
|
+
- `ctx.auth.listSecondFactors` lists the second factors from every running plugin
|
|
11
|
+
- `AuthEnrollmentSections` takes `factorsOnly`
|
|
12
|
+
- `getHttpsSettings` and `updateHttpsSettings` in `@termix-ssh/plugin-sdk/ui`
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- Plugin frontend tests against a locally linked SDK failed with a second copy of React
|
|
17
|
+
|
|
18
|
+
## 1.0.14
|
|
19
|
+
|
|
20
|
+
Plugin API 1.6.0.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- `ctx.hosts.locks.provide` and the `hosts:lock` capability, so a plugin can manage hosts that stay read only for everyone else and block adding hosts by hand
|
|
25
|
+
- `ctx.hosts.setShares` to replace every grant on a host in one call
|
|
26
|
+
- `ctx.credentials.list` returns the acting user's credentials by name, without secrets
|
|
27
|
+
- `ctx.hosts.create` and `update` take `password`, `key`, `keyPassword`, `keyType`, `sudoPassword`, `protocolAuth` and `pluginEnable`
|
|
28
|
+
|
|
3
29
|
## 1.0.13
|
|
4
30
|
|
|
5
31
|
Plugin API 1.5.0.
|
package/PLUGIN-GUIDE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Termix plugin guide
|
|
2
2
|
|
|
3
|
-
SDK 1.0.
|
|
3
|
+
SDK 1.0.15 | Plugin API 1.7.0
|
|
4
4
|
|
|
5
5
|
This guide is for anyone, person or AI coding tool, building a Termix plugin. It ships inside
|
|
6
6
|
`@termix-ssh/plugin-sdk`, so the copy in your `node_modules` always matches the SDK you have installed.
|
|
@@ -226,6 +226,7 @@ Bundled as ESM for Node 22+ into `dist/backend.js`. Backend imports of your own
|
|
|
226
226
|
| `sync` | none | Sync your tables to a linked desktop |
|
|
227
227
|
| `auth` | `auth:provide` | Login methods, second factors, SSH auth types |
|
|
228
228
|
| `auth.issueUserToken` | `users:impersonate` | A short-lived session for calling Termix's API as a user |
|
|
229
|
+
| `auth.policy` | `auth:policy` | Login rules: idle timeout, session length, second factors |
|
|
229
230
|
| `users.getLocale` | `users:read` | A user's UI language |
|
|
230
231
|
| `system` | `system:tls` | The server's HTTPS certificate |
|
|
231
232
|
| `plugins.list()` | `plugins:read` | Which plugins are installed |
|
|
@@ -300,6 +301,11 @@ router.post("/notes", async (req, res) => {
|
|
|
300
301
|
- `ctx.rbac.require("manage")` on a single route, `await ctx.rbac.has("manage")` in code. A full id like
|
|
301
302
|
`hosts.edit` checks a core permission.
|
|
302
303
|
- Host access: `ctx.hosts.checkAccess(hostId, "connect" | "view" | "edit" | "manage")`.
|
|
304
|
+
- Managed hosts: with `hosts:lock`, `ctx.hosts.locks.provide({ isLocked, note, allowManualCreate })` makes
|
|
305
|
+
the hosts you manage read only for the UI, the API and other plugins (pins and order still work), and
|
|
306
|
+
`allowManualCreate: () => false` blocks adding hosts by hand. Your own `ctx.hosts` writes still go
|
|
307
|
+
through. `ctx.hosts.setShares(hostId, grants)` replaces a host's whole grant list, and
|
|
308
|
+
`ctx.credentials.list()` finds the acting user's credentials by name.
|
|
303
309
|
|
|
304
310
|
### Errors
|
|
305
311
|
|
|
@@ -694,6 +700,13 @@ closely. Declare ids in `contributes.auth` and register them with `ctx.auth.regi
|
|
|
694
700
|
`registerSecondFactor` or `registerSshAuthProvider`. Connection protocols go in `contributes.protocols`
|
|
695
701
|
and `app.registerHostProtocol`. Read https://docs.termix.site/develop/auth before starting one.
|
|
696
702
|
|
|
703
|
+
Login rules need `auth:policy`. `ctx.auth.policy.provide({ evaluate, allowRememberDevice })` is asked at
|
|
704
|
+
every login with the user, the method, the device and every second factor (`ctx.auth.listSecondFactors()`
|
|
705
|
+
lists them too). `evaluate` returns `allowRememberDevice`, `maxSessionSeconds`, `idleTimeoutSeconds` and
|
|
706
|
+
`secondFactor` (`"always"` or `"new-device"`). The strictest answer from all plugins wins, and a throw
|
|
707
|
+
refuses the login. A user who must have a second factor but has none gets a session that can only set one
|
|
708
|
+
up, and `/users/me` reports `restriction: "second-factor-setup"`.
|
|
709
|
+
|
|
697
710
|
## Testing
|
|
698
711
|
|
|
699
712
|
```ts
|
|
@@ -890,6 +903,7 @@ To run your own registry instead, see https://docs.termix.site/develop/registrie
|
|
|
890
903
|
| `users:impersonate` | high | Act as another user, or get a session for them, audited |
|
|
891
904
|
| `db:core-refs` | high | Query core users, roles and hosts tables (read only) |
|
|
892
905
|
| `auth:provide` | high | Add login methods, second factors, SSH auth types |
|
|
906
|
+
| `auth:policy` | high | Set login rules: idle timeout, session length, 2FA |
|
|
893
907
|
| `device:serial` | high | Open serial and USB ports on the server |
|
|
894
908
|
| `desktop:local` | high | Use the user's own computer, desktop app only |
|
|
895
909
|
| `hosts:export` | high | Export hosts with their saved logins |
|
|
@@ -898,6 +912,7 @@ To run your own registry instead, see https://docs.termix.site/develop/registrie
|
|
|
898
912
|
| `hosts:write` | medium | Add and edit hosts |
|
|
899
913
|
| `hosts:status-provide` | medium | Be the source of host status dots |
|
|
900
914
|
| `hosts:activity-provide` | medium | Be the source of the recent activity list |
|
|
915
|
+
| `hosts:lock` | medium | Make hosts it manages read only for everyone else |
|
|
901
916
|
| `network:probe` | medium | Check that a host answers on a TCP port |
|
|
902
917
|
| `credentials:use` | medium | Connect with saved credentials without seeing them |
|
|
903
918
|
| `credentials:write` | medium | Save new credentials |
|
package/dist/backend.d.ts
CHANGED
|
@@ -591,11 +591,40 @@ export interface PluginHostCreateInput {
|
|
|
591
591
|
sshOptions?: HostSshOptions | null;
|
|
592
592
|
/** Host settings by plugin id, the shape a Termix export carries. */
|
|
593
593
|
pluginSettings?: Record<string, Record<string, unknown>>;
|
|
594
|
+
/**
|
|
595
|
+
* Each plugin's own host switch (its manifest enableKey), by plugin id.
|
|
596
|
+
* A plugin that is not running is skipped and logged; one with no switch
|
|
597
|
+
* is refused.
|
|
598
|
+
*/
|
|
599
|
+
pluginEnable?: Record<string, boolean>;
|
|
600
|
+
/**
|
|
601
|
+
* Secrets, stored encrypted with the owner's data key and never returned
|
|
602
|
+
* in a PluginHostRecord. An empty string or null clears the stored value.
|
|
603
|
+
*/
|
|
604
|
+
password?: string | null;
|
|
605
|
+
key?: string | null;
|
|
606
|
+
keyPassword?: string | null;
|
|
607
|
+
keyType?: string | null;
|
|
608
|
+
sudoPassword?: string | null;
|
|
609
|
+
/**
|
|
610
|
+
* Logins for plugin protocols (contributes.protocols), keyed by protocol
|
|
611
|
+
* id. null removes that protocol's login. Unknown protocol ids are skipped.
|
|
612
|
+
*/
|
|
613
|
+
protocolAuth?: Record<string, PluginHostProtocolAuthInput | null>;
|
|
614
|
+
}
|
|
615
|
+
/** One protocol login on a host write. Absent keys keep what is stored. */
|
|
616
|
+
export interface PluginHostProtocolAuthInput {
|
|
617
|
+
authType?: "direct" | "credential" | "none";
|
|
618
|
+
credentialId?: number | null;
|
|
619
|
+
username?: string | null;
|
|
620
|
+
password?: string | null;
|
|
621
|
+
/** The protocol's declared credentialFields, secret ones included. */
|
|
622
|
+
fields?: Record<string, string>;
|
|
594
623
|
}
|
|
595
624
|
/** Fields a plugin may change on a host the acting user owns. */
|
|
596
625
|
export type PluginHostUpdateInput = Partial<PluginHostCreateInput>;
|
|
597
626
|
/** Every key PluginHostCreateInput and PluginHostUpdateInput accept. */
|
|
598
|
-
export declare const PLUGIN_HOST_INPUT_KEYS: readonly ["name", "ip", "port", "username", "authType", "credentialId", "overrideCredentialUsername", "connectionType", "tags", "folder", "pin", "notes", "jumpHosts", "enableSsh", "sshPort", "statusCheckEnabled", "statusCheckInterval", "forceKeyboardInteractive", "sshOptions", "pluginSettings"];
|
|
627
|
+
export declare const PLUGIN_HOST_INPUT_KEYS: readonly ["name", "ip", "port", "username", "authType", "credentialId", "overrideCredentialUsername", "connectionType", "tags", "folder", "pin", "notes", "jumpHosts", "enableSsh", "sshPort", "statusCheckEnabled", "statusCheckInterval", "forceKeyboardInteractive", "sshOptions", "pluginSettings", "pluginEnable", "password", "key", "keyPassword", "keyType", "sudoPassword", "protocolAuth"];
|
|
599
628
|
/** The same shape canAccessHost returns internally, without secrets. */
|
|
600
629
|
export interface PluginHostAccess {
|
|
601
630
|
hasAccess: boolean;
|
|
@@ -624,6 +653,31 @@ export interface PluginShareableRole {
|
|
|
624
653
|
name: string;
|
|
625
654
|
displayName: string | null;
|
|
626
655
|
}
|
|
656
|
+
/** One grant in ctx.hosts.setShares. */
|
|
657
|
+
export interface PluginHostShareGrant {
|
|
658
|
+
target: PluginShareTarget;
|
|
659
|
+
permissionLevel: PluginHostShareLevel;
|
|
660
|
+
/** Omit for a grant that never expires. */
|
|
661
|
+
durationHours?: number;
|
|
662
|
+
}
|
|
663
|
+
/**
|
|
664
|
+
* Hosts a plugin manages itself, for example from a config file. Core refuses
|
|
665
|
+
* edits, deletes and share changes on a locked host from the UI, the API and
|
|
666
|
+
* other plugins, and marks it in host reads so a layout can show it read only.
|
|
667
|
+
* Pins and order stay with the user. Only the plugin that locks a host may
|
|
668
|
+
* still write it.
|
|
669
|
+
*/
|
|
670
|
+
export interface PluginHostLockProvider {
|
|
671
|
+
isLocked: (hostId: number) => boolean;
|
|
672
|
+
/** A short line shown under the managed note, or null. */
|
|
673
|
+
note: (hostId: number) => string | null;
|
|
674
|
+
/** False blocks adding hosts by hand (editor, import, other plugins). */
|
|
675
|
+
allowManualCreate: () => boolean;
|
|
676
|
+
}
|
|
677
|
+
export interface PluginHostLocksApi {
|
|
678
|
+
/** Registers this plugin's locks. Needs hosts:lock. Removed on deactivate. */
|
|
679
|
+
provide: (provider: PluginHostLockProvider) => () => void;
|
|
680
|
+
}
|
|
627
681
|
/**
|
|
628
682
|
* Hosts the acting user can see, and the RBAC + sharing operations a plugin
|
|
629
683
|
* needs to build a feature (like fleets) on top of hosts it does not own.
|
|
@@ -675,6 +729,12 @@ export interface PluginHosts {
|
|
|
675
729
|
* Refuses a host the caller does not hold "manage" on.
|
|
676
730
|
*/
|
|
677
731
|
share: (hostId: number, targets: PluginShareTarget[], permissionLevel: PluginHostShareLevel, durationHours?: number) => Promise<PluginHostShareResult>;
|
|
732
|
+
/**
|
|
733
|
+
* Replaces every grant on a host the acting user owns with this list.
|
|
734
|
+
* Grants that stay keep their row and counters, missing ones are revoked.
|
|
735
|
+
* Needs hosts:write.
|
|
736
|
+
*/
|
|
737
|
+
setShares: (hostId: number, grants: PluginHostShareGrant[]) => Promise<PluginHostShareResult>;
|
|
678
738
|
/** Users the caller may pick as a share target, for building a picker UI. */
|
|
679
739
|
listUsers: () => Promise<PluginShareableUser[]>;
|
|
680
740
|
/** Non-system roles the caller may pick as a share target. */
|
|
@@ -736,6 +796,8 @@ export interface PluginHosts {
|
|
|
736
796
|
registerImportNormalizer: (normalizer: PluginHostImportNormalizer) => () => void;
|
|
737
797
|
/** The recent activity list on the dashboard. */
|
|
738
798
|
activity: PluginHostActivityApi;
|
|
799
|
+
/** Read only hosts this plugin manages. */
|
|
800
|
+
locks: PluginHostLocksApi;
|
|
739
801
|
}
|
|
740
802
|
export interface PluginHostImportOptions {
|
|
741
803
|
/** Update a host that matches on ip, port and username instead of skipping it. */
|
|
@@ -1416,6 +1478,52 @@ export interface PluginSecondFactor {
|
|
|
1416
1478
|
/** Removes the user's enrolment. Called by the admin reset. */
|
|
1417
1479
|
reset?: (userId: string) => Promise<void>;
|
|
1418
1480
|
}
|
|
1481
|
+
/** A login core is about to turn into a session. */
|
|
1482
|
+
export interface PluginAuthPolicyInput {
|
|
1483
|
+
userId: string;
|
|
1484
|
+
/** Login method id, `"proxy"` for a trusted proxy login. */
|
|
1485
|
+
methodId: string;
|
|
1486
|
+
/** The method signs in through an outside identity provider (SSO, LDAP). */
|
|
1487
|
+
external: boolean;
|
|
1488
|
+
deviceType: "web" | "desktop" | "mobile";
|
|
1489
|
+
/** The device passed a second factor before and was remembered. */
|
|
1490
|
+
trustedDevice: boolean;
|
|
1491
|
+
/** The method itself proved a second factor, like a verified passkey. */
|
|
1492
|
+
mfaSatisfied: boolean;
|
|
1493
|
+
/** Every second factor core has, and whether this user set it up. */
|
|
1494
|
+
secondFactors: {
|
|
1495
|
+
id: string;
|
|
1496
|
+
pluginId: string;
|
|
1497
|
+
enrolled: boolean;
|
|
1498
|
+
}[];
|
|
1499
|
+
}
|
|
1500
|
+
export interface PluginAuthPolicyDecision {
|
|
1501
|
+
/** False turns remember me off and ignores remembered devices. */
|
|
1502
|
+
allowRememberDevice?: boolean;
|
|
1503
|
+
/** Caps how long the session lasts. */
|
|
1504
|
+
maxSessionSeconds?: number;
|
|
1505
|
+
/** Ends the session after this many seconds without a request. */
|
|
1506
|
+
idleTimeoutSeconds?: number;
|
|
1507
|
+
/**
|
|
1508
|
+
* Requires a second factor even for users who have none: they get a
|
|
1509
|
+
* session that can only set one up. `"new-device"` skips it on a
|
|
1510
|
+
* remembered device.
|
|
1511
|
+
*/
|
|
1512
|
+
secondFactor?: "always" | "new-device";
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Login rules from a plugin. Core asks every provider and the strictest
|
|
1516
|
+
* answer wins. A provider that throws refuses the login.
|
|
1517
|
+
*/
|
|
1518
|
+
export interface PluginAuthPolicyProvider {
|
|
1519
|
+
evaluate: (input: PluginAuthPolicyInput) => PluginAuthPolicyDecision | Promise<PluginAuthPolicyDecision>;
|
|
1520
|
+
/** Whether the login page should offer remember me. */
|
|
1521
|
+
allowRememberDevice?: () => boolean;
|
|
1522
|
+
}
|
|
1523
|
+
export interface PluginAuthPolicyApi {
|
|
1524
|
+
/** Needs auth:policy. Removed on deactivate. */
|
|
1525
|
+
provide: (provider: PluginAuthPolicyProvider) => () => void;
|
|
1526
|
+
}
|
|
1419
1527
|
export interface PluginUserTokenOptions {
|
|
1420
1528
|
/** Why the token exists, kept in the audit log and on the session. */
|
|
1421
1529
|
purpose: string;
|
|
@@ -1494,6 +1602,13 @@ export interface PluginAuth {
|
|
|
1494
1602
|
* never outlive itself.
|
|
1495
1603
|
*/
|
|
1496
1604
|
issueUserToken: (userId: string, options: PluginUserTokenOptions) => Promise<PluginUserToken>;
|
|
1605
|
+
/** Login rules: idle timeout, session length, remember me, second factors. */
|
|
1606
|
+
policy: PluginAuthPolicyApi;
|
|
1607
|
+
/** Second factors from every running plugin. Needs no capability. */
|
|
1608
|
+
listSecondFactors: () => {
|
|
1609
|
+
id: string;
|
|
1610
|
+
pluginId: string;
|
|
1611
|
+
}[];
|
|
1497
1612
|
}
|
|
1498
1613
|
/** What a plugin may learn about a user. Needs users:read. */
|
|
1499
1614
|
export interface PluginUsers {
|
|
@@ -1727,6 +1842,14 @@ export interface PluginSshKeyCredential {
|
|
|
1727
1842
|
/** "<type> <base64>", derived from the private key when none was stored. Null when it cannot be. */
|
|
1728
1843
|
publicKey: string | null;
|
|
1729
1844
|
}
|
|
1845
|
+
/** A saved credential without its secrets. */
|
|
1846
|
+
export interface PluginCredentialSummary {
|
|
1847
|
+
id: number;
|
|
1848
|
+
name: string;
|
|
1849
|
+
username: string | null;
|
|
1850
|
+
authType: string;
|
|
1851
|
+
folder: string | null;
|
|
1852
|
+
}
|
|
1730
1853
|
/** A key pair to save as a new credential for the acting user. */
|
|
1731
1854
|
export interface PluginSshKeyCredentialInput {
|
|
1732
1855
|
name: string;
|
|
@@ -1747,6 +1870,12 @@ export interface PluginCredentials {
|
|
|
1747
1870
|
* Needs credentials:use and the core credentials.view permission. Audited.
|
|
1748
1871
|
*/
|
|
1749
1872
|
listSshKeys: () => Promise<PluginSshKeyCredential[]>;
|
|
1873
|
+
/**
|
|
1874
|
+
* The acting user's own credentials by name, with no secret material, so a
|
|
1875
|
+
* plugin can point a host at one. Needs credentials:use and the core
|
|
1876
|
+
* credentials.view permission. Audited.
|
|
1877
|
+
*/
|
|
1878
|
+
list: () => Promise<PluginCredentialSummary[]>;
|
|
1750
1879
|
/**
|
|
1751
1880
|
* Saves a key pair as a new encrypted credential owned by the acting user
|
|
1752
1881
|
* and returns its id. Needs credentials:write, the core credentials.create
|
package/dist/backend.js
CHANGED
|
@@ -65,6 +65,13 @@ export const PLUGIN_HOST_INPUT_KEYS = [
|
|
|
65
65
|
"forceKeyboardInteractive",
|
|
66
66
|
"sshOptions",
|
|
67
67
|
"pluginSettings",
|
|
68
|
+
"pluginEnable",
|
|
69
|
+
"password",
|
|
70
|
+
"key",
|
|
71
|
+
"keyPassword",
|
|
72
|
+
"keyType",
|
|
73
|
+
"sudoPassword",
|
|
74
|
+
"protocolAuth",
|
|
68
75
|
];
|
|
69
76
|
/** Thrown by ctx.ssh.startInteraction when no provider can start it. */
|
|
70
77
|
export class PluginSshInteractionError extends Error {
|
package/dist/capabilities.js
CHANGED
|
@@ -33,6 +33,7 @@ export const CAPABILITY_CATALOG = [
|
|
|
33
33
|
entry("users:impersonate", "high"),
|
|
34
34
|
entry("db:core-refs", "high"),
|
|
35
35
|
entry("auth:provide", "high"),
|
|
36
|
+
entry("auth:policy", "high"),
|
|
36
37
|
entry("device:serial", "high"),
|
|
37
38
|
entry("notify:hub", "high"),
|
|
38
39
|
entry("ui:layout", "high"),
|
|
@@ -46,6 +47,7 @@ export const CAPABILITY_CATALOG = [
|
|
|
46
47
|
entry("network:probe", "medium"),
|
|
47
48
|
entry("hosts:status-provide", "medium"),
|
|
48
49
|
entry("hosts:activity-provide", "medium"),
|
|
50
|
+
entry("hosts:lock", "medium"),
|
|
49
51
|
entry("users:read", "medium"),
|
|
50
52
|
entry("events:core", "medium"),
|
|
51
53
|
entry("notify:send", "medium"),
|
|
@@ -29,7 +29,7 @@ import {
|
|
|
29
29
|
sortPlugins,
|
|
30
30
|
uploadEntry,
|
|
31
31
|
youtubeEmbedUrl
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-VFTB2RDP.js";
|
|
33
33
|
import {
|
|
34
34
|
ArrowDownWideNarrow,
|
|
35
35
|
BookOpen,
|
|
@@ -107,8 +107,8 @@ import {
|
|
|
107
107
|
updatePlugin,
|
|
108
108
|
uploadPlugin,
|
|
109
109
|
useConfirm
|
|
110
|
-
} from "./chunk-
|
|
111
|
-
import "./chunk-
|
|
110
|
+
} from "./chunk-2IV3VX7F.js";
|
|
111
|
+
import "./chunk-3L45NNT5.js";
|
|
112
112
|
import {
|
|
113
113
|
__toESM,
|
|
114
114
|
define_import_meta_env_default,
|
|
@@ -4932,6 +4932,14 @@ async function bulkUpdateSSHHosts(hostIds, updates) {
|
|
|
4932
4932
|
handleApiError(error, "bulk update SSH hosts");
|
|
4933
4933
|
}
|
|
4934
4934
|
}
|
|
4935
|
+
async function getHostPolicy() {
|
|
4936
|
+
try {
|
|
4937
|
+
const response = await sshHostApi.get("/policy");
|
|
4938
|
+
return response.data;
|
|
4939
|
+
} catch (error) {
|
|
4940
|
+
handleApiError(error, "load host policy");
|
|
4941
|
+
}
|
|
4942
|
+
}
|
|
4935
4943
|
async function reorderSSHHosts(positions) {
|
|
4936
4944
|
try {
|
|
4937
4945
|
const response = await sshHostApi.put("/reorder", { positions });
|
|
@@ -5588,6 +5596,24 @@ async function updateSessionTimeout(timeoutHours) {
|
|
|
5588
5596
|
handleApiError(error, "update session timeout");
|
|
5589
5597
|
}
|
|
5590
5598
|
}
|
|
5599
|
+
async function getHttpsSettings() {
|
|
5600
|
+
try {
|
|
5601
|
+
const response = await authApi.get("/users/https-settings");
|
|
5602
|
+
return response.data;
|
|
5603
|
+
} catch (error) {
|
|
5604
|
+
handleApiError(error, "fetch HTTPS settings");
|
|
5605
|
+
}
|
|
5606
|
+
}
|
|
5607
|
+
async function updateHttpsSettings(forceHttps) {
|
|
5608
|
+
try {
|
|
5609
|
+
const response = await authApi.patch("/users/https-settings", {
|
|
5610
|
+
forceHttps
|
|
5611
|
+
});
|
|
5612
|
+
return response.data;
|
|
5613
|
+
} catch (error) {
|
|
5614
|
+
handleApiError(error, "update HTTPS settings");
|
|
5615
|
+
}
|
|
5616
|
+
}
|
|
5591
5617
|
async function getBranding() {
|
|
5592
5618
|
try {
|
|
5593
5619
|
const response = await authApi.get("/users/branding");
|
|
@@ -6290,7 +6316,7 @@ async function updateRole(roleId, roleData) {
|
|
|
6290
6316
|
try {
|
|
6291
6317
|
const response = await rbacApi.put(`/rbac/roles/${roleId}`, roleData);
|
|
6292
6318
|
if (roleData.permissions) {
|
|
6293
|
-
const { notifyPermissionsChanged } = await import("./use-permissions-
|
|
6319
|
+
const { notifyPermissionsChanged } = await import("./use-permissions-LLN6TLAL.js");
|
|
6294
6320
|
notifyPermissionsChanged();
|
|
6295
6321
|
}
|
|
6296
6322
|
return response.data;
|
|
@@ -6872,7 +6898,7 @@ function createRemoteOriginApiInstance(path) {
|
|
|
6872
6898
|
});
|
|
6873
6899
|
instance.interceptors.request.use(
|
|
6874
6900
|
async (config) => {
|
|
6875
|
-
const { getLinkedSession: getLinkedSession2 } = await import("./linked-server-
|
|
6901
|
+
const { getLinkedSession: getLinkedSession2 } = await import("./linked-server-SCJH7DSL.js");
|
|
6876
6902
|
const linked = await getLinkedSession2();
|
|
6877
6903
|
config.baseURL = linked ? `${linked.serverUrl}${path}` : "http://no-server-configured";
|
|
6878
6904
|
if (config.headers.set) {
|
|
@@ -7300,6 +7326,7 @@ export {
|
|
|
7300
7326
|
createSSHHost,
|
|
7301
7327
|
updateSSHHost,
|
|
7302
7328
|
bulkUpdateSSHHosts,
|
|
7329
|
+
getHostPolicy,
|
|
7303
7330
|
reorderSSHHosts,
|
|
7304
7331
|
deleteSSHHost,
|
|
7305
7332
|
websocketAuthProtocols,
|
|
@@ -7328,6 +7355,8 @@ export {
|
|
|
7328
7355
|
updateLogLevel,
|
|
7329
7356
|
getSessionTimeout,
|
|
7330
7357
|
updateSessionTimeout,
|
|
7358
|
+
getHttpsSettings,
|
|
7359
|
+
updateHttpsSettings,
|
|
7331
7360
|
getBranding,
|
|
7332
7361
|
hydrateLocalSharedHostAuth,
|
|
7333
7362
|
getConnectedRemoteApi,
|
|
@@ -26,7 +26,7 @@ var inFlight = null;
|
|
|
26
26
|
function load() {
|
|
27
27
|
if (cache) return Promise.resolve(cache);
|
|
28
28
|
if (inFlight) return inFlight;
|
|
29
|
-
inFlight = import("./rbac-api-
|
|
29
|
+
inFlight = import("./rbac-api-KA5IPQQL.js").then(({ getMyPermissions }) => getMyPermissions()).then((result) => {
|
|
30
30
|
cache = {
|
|
31
31
|
permissions: result.permissions ?? [],
|
|
32
32
|
isAdmin: !!result.isAdmin
|
|
@@ -3,7 +3,7 @@ import {
|
|
|
3
3
|
hasPermission,
|
|
4
4
|
resetPermissionsCache,
|
|
5
5
|
usePermissions
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-57DJU2ZA.js";
|
|
7
7
|
import {
|
|
8
8
|
Button,
|
|
9
9
|
Check,
|
|
@@ -80,7 +80,7 @@ import {
|
|
|
80
80
|
withIconBoundary,
|
|
81
81
|
withPluginContext,
|
|
82
82
|
withPluginScope
|
|
83
|
-
} from "./chunk-
|
|
83
|
+
} from "./chunk-2IV3VX7F.js";
|
|
84
84
|
import {
|
|
85
85
|
SYNC_CHANGED_EVENT,
|
|
86
86
|
TRUST_TOKEN_KEY,
|
|
@@ -130,7 +130,7 @@ import {
|
|
|
130
130
|
usePluginStore,
|
|
131
131
|
useTabTypes,
|
|
132
132
|
websocketAuthProtocols
|
|
133
|
-
} from "./chunk-
|
|
133
|
+
} from "./chunk-3L45NNT5.js";
|
|
134
134
|
import {
|
|
135
135
|
define_import_meta_env_default,
|
|
136
136
|
init_define_import_meta_env
|
|
@@ -268,6 +268,14 @@ async function getLoginMethods() {
|
|
|
268
268
|
return [];
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
|
+
async function getRememberDeviceAllowed() {
|
|
272
|
+
try {
|
|
273
|
+
const response = await authApi.get("/users/auth/methods");
|
|
274
|
+
return response.data?.rememberDevice !== false;
|
|
275
|
+
} catch {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
271
279
|
async function submitLoginMethod(methodId, body, instanceId) {
|
|
272
280
|
try {
|
|
273
281
|
const response = await authApi.post(
|
|
@@ -1447,6 +1455,9 @@ var en_default = {
|
|
|
1447
1455
|
deleteFolderConfirm_one: 'Delete "{{name}}" and its {{count}} host? This cannot be undone.',
|
|
1448
1456
|
deleteFolderConfirm_other: 'Delete "{{name}}" and its {{count}} hosts? This cannot be undone.',
|
|
1449
1457
|
failedToMoveHosts: "Failed to move hosts",
|
|
1458
|
+
managedBanner: "This host is managed by {{plugin}}. Change it there. You can still pin and reorder it.",
|
|
1459
|
+
managedTooltip: "Managed by {{plugin}}",
|
|
1460
|
+
addBlocked: "Hosts are managed by {{plugin}}, so new hosts can't be added here.",
|
|
1450
1461
|
failedToReorder: "Failed to reorder",
|
|
1451
1462
|
expandAll: "Expand all folders",
|
|
1452
1463
|
collapseAll: "Collapse all folders",
|
|
@@ -2043,6 +2054,10 @@ var en_default = {
|
|
|
2043
2054
|
title: "Handle sign in",
|
|
2044
2055
|
consequence: "It can add a login method, a second factor or an SSH sign-in type."
|
|
2045
2056
|
},
|
|
2057
|
+
"auth:policy": {
|
|
2058
|
+
title: "Set sign in rules",
|
|
2059
|
+
consequence: "It can sign people out when idle, limit session length, turn off remember me and require a second factor."
|
|
2060
|
+
},
|
|
2046
2061
|
"system:tls": {
|
|
2047
2062
|
title: "Manage certificates",
|
|
2048
2063
|
consequence: "It can replace the certificate Termix serves over HTTPS and answer domain checks from certificate authorities."
|
|
@@ -2111,6 +2126,10 @@ var en_default = {
|
|
|
2111
2126
|
title: "Store recent activity",
|
|
2112
2127
|
consequence: "Becomes the store for the recent activity list of every user."
|
|
2113
2128
|
},
|
|
2129
|
+
"hosts:lock": {
|
|
2130
|
+
title: "Manage hosts as read only",
|
|
2131
|
+
consequence: "Hosts it manages can't be edited, deleted or reshared by anyone else."
|
|
2132
|
+
},
|
|
2114
2133
|
"desktop:window": {
|
|
2115
2134
|
title: "Open desktop windows",
|
|
2116
2135
|
consequence: "Only in the Termix desktop app."
|
|
@@ -4103,6 +4122,7 @@ function sshHostToHost(h) {
|
|
|
4103
4122
|
connectionOrigin: h.connectionOrigin ?? null,
|
|
4104
4123
|
localOnly: !!h.localOnly,
|
|
4105
4124
|
sharedCopy: !!h.sharedCopy,
|
|
4125
|
+
...h.lock ? { lock: h.lock } : {},
|
|
4106
4126
|
enableSsh: h.enableSsh != null ? h.enableSsh : isSshHost,
|
|
4107
4127
|
sshPort: h.sshPort ?? (h.connectionType === "ssh" || !h.connectionType ? h.port : 22),
|
|
4108
4128
|
protocolAuth: h.protocolAuth ?? {},
|
|
@@ -10040,7 +10060,7 @@ function hostToSSHHost(h) {
|
|
|
10040
10060
|
// src/ui/shell/tabUtils.tsx
|
|
10041
10061
|
import { jsx as jsx13, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
10042
10062
|
var PluginsTab = lazy(
|
|
10043
|
-
() => import("./PluginsTab-
|
|
10063
|
+
() => import("./PluginsTab-S33WIIGC.js").then((m) => ({ default: m.PluginsTab }))
|
|
10044
10064
|
);
|
|
10045
10065
|
function preloadTabSurface(type) {
|
|
10046
10066
|
const loader = getTabType(type)?.preload;
|
|
@@ -10225,6 +10245,7 @@ export {
|
|
|
10225
10245
|
remScale,
|
|
10226
10246
|
useIsMobile,
|
|
10227
10247
|
getLoginMethods,
|
|
10248
|
+
getRememberDeviceAllowed,
|
|
10228
10249
|
submitLoginMethod,
|
|
10229
10250
|
startLoginRedirect,
|
|
10230
10251
|
verifySecondFactor,
|
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
Trash,
|
|
8
8
|
cn,
|
|
9
9
|
useAreaPreferences
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-2IV3VX7F.js";
|
|
11
11
|
import {
|
|
12
12
|
isElectron
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-3L45NNT5.js";
|
|
14
14
|
import {
|
|
15
15
|
__commonJS,
|
|
16
16
|
__toESM,
|
|
@@ -2382,6 +2382,7 @@ var CAPABILITY_CATALOG = [
|
|
|
2382
2382
|
entry("users:impersonate", "high"),
|
|
2383
2383
|
entry("db:core-refs", "high"),
|
|
2384
2384
|
entry("auth:provide", "high"),
|
|
2385
|
+
entry("auth:policy", "high"),
|
|
2385
2386
|
entry("device:serial", "high"),
|
|
2386
2387
|
entry("notify:hub", "high"),
|
|
2387
2388
|
entry("ui:layout", "high"),
|
|
@@ -2395,6 +2396,7 @@ var CAPABILITY_CATALOG = [
|
|
|
2395
2396
|
entry("network:probe", "medium"),
|
|
2396
2397
|
entry("hosts:status-provide", "medium"),
|
|
2397
2398
|
entry("hosts:activity-provide", "medium"),
|
|
2399
|
+
entry("hosts:lock", "medium"),
|
|
2398
2400
|
entry("users:read", "medium"),
|
|
2399
2401
|
entry("events:core", "medium"),
|
|
2400
2402
|
entry("notify:send", "medium"),
|
|
@@ -59,10 +59,10 @@ import {
|
|
|
59
59
|
trackTabHandle,
|
|
60
60
|
useShellController,
|
|
61
61
|
useTabStageStore
|
|
62
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-V44FCJBQ.js";
|
|
63
63
|
import {
|
|
64
64
|
setPermissionsForTesting
|
|
65
|
-
} from "./chunk-
|
|
65
|
+
} from "./chunk-57DJU2ZA.js";
|
|
66
66
|
import {
|
|
67
67
|
SurfaceScope,
|
|
68
68
|
notifyShellReady,
|
|
@@ -70,14 +70,14 @@ import {
|
|
|
70
70
|
setShellCallbacks,
|
|
71
71
|
setShellHosts,
|
|
72
72
|
setShellLayoutProvider
|
|
73
|
-
} from "./chunk-
|
|
73
|
+
} from "./chunk-2IV3VX7F.js";
|
|
74
74
|
import {
|
|
75
75
|
getTabType,
|
|
76
76
|
listTabTypes,
|
|
77
77
|
resetPluginStore,
|
|
78
78
|
setFrontendState,
|
|
79
79
|
setPluginSummaries
|
|
80
|
-
} from "./chunk-
|
|
80
|
+
} from "./chunk-3L45NNT5.js";
|
|
81
81
|
import {
|
|
82
82
|
init_define_import_meta_env
|
|
83
83
|
} from "./chunk-YGYBM7SQ.js";
|
|
@@ -23,6 +23,12 @@ export interface HostProtocolAuthInput {
|
|
|
23
23
|
export interface JumpHost {
|
|
24
24
|
hostId: number;
|
|
25
25
|
}
|
|
26
|
+
/** The plugin that manages a host, from ctx.hosts.locks. */
|
|
27
|
+
export interface HostLock {
|
|
28
|
+
pluginId: string;
|
|
29
|
+
pluginName: string;
|
|
30
|
+
note: string | null;
|
|
31
|
+
}
|
|
26
32
|
export type Host = {
|
|
27
33
|
id: number;
|
|
28
34
|
name: string;
|
|
@@ -91,6 +97,8 @@ export type Host = {
|
|
|
91
97
|
ownerUsername?: string;
|
|
92
98
|
/** A read-only copy of a host shared with the linked account. */
|
|
93
99
|
sharedCopy?: boolean;
|
|
100
|
+
/** Set when a plugin manages this host: read only, pins and order still work. */
|
|
101
|
+
lock?: HostLock;
|
|
94
102
|
/** Desktop only: kept on this device, never synced to the server. */
|
|
95
103
|
localOnly?: boolean;
|
|
96
104
|
/** Enabled plugins' host-scope settings, keyed by plugin id. Secrets redacted. */
|
|
@@ -261,6 +269,8 @@ export interface AuthenticatedRequest extends Request {
|
|
|
261
269
|
sessionId?: string;
|
|
262
270
|
apiKeyId?: string;
|
|
263
271
|
actingAdminUserId?: string;
|
|
272
|
+
/** Set while the session may only set up a second factor. */
|
|
273
|
+
sessionRestriction?: string | null;
|
|
264
274
|
user?: {
|
|
265
275
|
id: string;
|
|
266
276
|
username: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HostProtocolAuthSummary, HostSshOptions, HostTerminalConfig, SSHAuthType } from "./index.js";
|
|
1
|
+
import type { HostLock, HostProtocolAuthSummary, HostSshOptions, HostTerminalConfig, SSHAuthType } from "./index.js";
|
|
2
2
|
import type { HostAuthOverrides } from "./auth-protocols.js";
|
|
3
3
|
import type { DefaultOverrides } from "./host-defaults.js";
|
|
4
4
|
import type { QuickConnectLogin } from "@termix-ssh/plugin-sdk/frontend";
|
|
@@ -93,6 +93,8 @@ export type Host = {
|
|
|
93
93
|
* linked to. It arrives through sync and is managed on the server.
|
|
94
94
|
*/
|
|
95
95
|
sharedCopy?: boolean;
|
|
96
|
+
/** Set when a plugin manages this host: read only, pins and order still work. */
|
|
97
|
+
lock?: HostLock;
|
|
96
98
|
/** Desktop only: kept on this device, never synced to the server. */
|
|
97
99
|
localOnly?: boolean;
|
|
98
100
|
};
|
|
@@ -23,6 +23,8 @@ export type LoginResponse = AuthResponse & {
|
|
|
23
23
|
};
|
|
24
24
|
/** Enabled login methods for the login screen. Public. */
|
|
25
25
|
export declare function getLoginMethods(): Promise<PublicLoginMethod[]>;
|
|
26
|
+
/** False when the login policy turned remember me off. Public. */
|
|
27
|
+
export declare function getRememberDeviceAllowed(): Promise<boolean>;
|
|
26
28
|
export declare function submitLoginMethod(methodId: string, body: Record<string, unknown>, instanceId?: string): Promise<LoginResponse>;
|
|
27
29
|
export declare function startLoginRedirect(methodId: string, params: {
|
|
28
30
|
instanceId?: string;
|
|
@@ -6,6 +6,12 @@ export declare function getSessionTimeout(): Promise<{
|
|
|
6
6
|
timeoutHours: number;
|
|
7
7
|
}>;
|
|
8
8
|
export declare function updateSessionTimeout(timeoutHours: number): Promise<void>;
|
|
9
|
+
export interface HttpsSettings {
|
|
10
|
+
forceHttps: boolean;
|
|
11
|
+
tlsEnabled: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare function getHttpsSettings(): Promise<HttpsSettings>;
|
|
14
|
+
export declare function updateHttpsSettings(forceHttps: boolean): Promise<HttpsSettings>;
|
|
9
15
|
export interface BrandingSettings {
|
|
10
16
|
appName: string;
|
|
11
17
|
tagline: string;
|
|
@@ -9,6 +9,12 @@ export declare function bulkUpdateSSHHosts(hostIds: number[], updates: Record<st
|
|
|
9
9
|
failed: number;
|
|
10
10
|
errors: string[];
|
|
11
11
|
}>;
|
|
12
|
+
export interface HostPolicy {
|
|
13
|
+
manualCreate: boolean;
|
|
14
|
+
/** The plugin that manages hosts, when hosts can't be added by hand. */
|
|
15
|
+
blockedBy?: string;
|
|
16
|
+
}
|
|
17
|
+
export declare function getHostPolicy(): Promise<HostPolicy>;
|
|
12
18
|
export declare function reorderSSHHosts(positions: {
|
|
13
19
|
id: number;
|
|
14
20
|
sortOrder: number;
|
|
@@ -103,12 +103,12 @@ export declare const appReadyPromise: Promise<void>;
|
|
|
103
103
|
*/
|
|
104
104
|
export declare function isLoginRequestUrl(url: string | undefined): boolean;
|
|
105
105
|
export declare function handleApiError(error: unknown, operation: string): never;
|
|
106
|
-
export { getSSHHosts, createSSHHost, updateSSHHost, bulkUpdateSSHHosts, reorderSSHHosts, deleteSSHHost, getSSHHostById, testProxyConnection, } from "./api/ssh-host-management-api";
|
|
106
|
+
export { getSSHHosts, createSSHHost, updateSSHHost, bulkUpdateSSHHosts, reorderSSHHosts, getHostPolicy, type HostPolicy, deleteSSHHost, getSSHHostById, testProxyConnection, } from "./api/ssh-host-management-api";
|
|
107
107
|
export { getAllServerStatuses, getServerStatusById, refreshServerPolling, getStatusCheckSettings, updateStatusCheckSettings, } from "./api/host-status-api";
|
|
108
108
|
export { getHostSidebarPreferences, saveHostSidebarPreferences, } from "./api/host-sidebar-preferences-api";
|
|
109
109
|
export { getCredentialSidebarPreferences, saveCredentialSidebarPreferences, } from "./api/credential-sidebar-preferences-api";
|
|
110
110
|
export { getUiPreferences, saveUiPreferences } from "./api/ui-preferences-api";
|
|
111
|
-
export { getLogLevel, updateLogLevel, getSessionTimeout, updateSessionTimeout, } from "./api/settings-api";
|
|
111
|
+
export { getLogLevel, updateLogLevel, getSessionTimeout, updateSessionTimeout, getHttpsSettings, updateHttpsSettings, type HttpsSettings, } from "./api/settings-api";
|
|
112
112
|
export declare function registerUser(username: string, password: string): Promise<Record<string, unknown>>;
|
|
113
113
|
export declare function adminCreateUser(username: string, password: string): Promise<Record<string, unknown>>;
|
|
114
114
|
export declare function loginUser(username: string, password: string, rememberMe?: boolean): Promise<AuthResponse>;
|
|
@@ -107,7 +107,7 @@ export { type AuthOverrideProtocol, SSH_AUTH_PROTOCOL, } from "../../types/auth-
|
|
|
107
107
|
export { CREDENTIAL_ROW_FIELD_KEYS, type CredentialDensity, type CredentialRowFields, type CredentialSidebarFilterState, type CredentialSidebarPreferences, type CredentialSortKey, type CredentialTrayTrigger, defaultCredentialRowFields, defaultCredentialSidebarPreferences, sanitizeCredentialSidebarPreferences, } from "../../types/credential-sidebar-preferences";
|
|
108
108
|
export { CORE_HOST_DEFAULTS, CORE_NAMESPACE, type DefaultOverrides, defaultValuesEqual, findCoreHostDefault, type HostDefaultsLevel, type ResolvedHostDefault, splitDefaultKey, } from "../../types/host-defaults";
|
|
109
109
|
export { defaultHostRowFields, defaultHostSidebarPreferences, type HostClickBehavior, type HostDensity, type HostGroupKey, type HostRowFields, type HostSidebarFilterState, type HostSidebarPreferences, type HostTrayTrigger, sanitizeHostSidebarPreferences, type SortKey, } from "../../types/host-sidebar-preferences";
|
|
110
|
-
export type { HostData, HostProtocolAuthInput, ProxyNode, SSHHost, SSHHostData, } from "../../types";
|
|
110
|
+
export type { HostData, HostLock, HostProtocolAuthInput, ProxyNode, SSHHost, SSHHostData, } from "../../types";
|
|
111
111
|
export { PRESETS, UI_AREA_KEYS, type UiAreaKey, type UiPreset, } from "../../types/ui-preferences";
|
|
112
112
|
export type { AdminSection, Credential, DashboardCardId, FontSizeId, Host, HostFolder, SharePermissionLevel, Tab, TabType, ThemeId, ToolsTab, UiFontId, } from "../../types/ui-types";
|
|
113
113
|
export { type AuditLog, type AuditLogFilters, getAuditLogActions, getAuditLogs, } from "../api/audit-log-api";
|
|
@@ -165,7 +165,7 @@ export { tabJumpHotkeyKeys } from "../lib/tab-jump-hotkey";
|
|
|
165
165
|
export { ACCENT_PRESET_COLORS, applyAccentColor, applyFontSize, applyUiFont, DASHBOARD_CARDS, FONT_SIZES, UI_FONTS, } from "../lib/theme";
|
|
166
166
|
export { registerLayout } from "../look/layout-registry";
|
|
167
167
|
export { useChooseTheme } from "../look/use-look-theme";
|
|
168
|
-
export { type AccessRecord, type ActiveSessionInfo, adminCreateUser, adminCreateUserCredential, adminCreateUserHost, adminDeleteUserCredential, adminDeleteUserHost, adminExportUserData, adminGetHostPassword, adminGetUserCredentials, adminGetUserHosts, adminResetUserPassword, adminUpdateUserCredential, adminUpdateUserHost, type ApiKey, assignRoleToUser, bulkUpdateSSHHosts, changePassword, createApiKey, createCredential, type CreatedApiKey, createRole, createSSHHost, deleteAccount, deleteAllHostsInFolder, deleteApiKey, deleteCredential, deleteOpenTab, deleteRole, deleteSSHHost, deleteUser, duplicateCredential, getActiveSessions, getApiKeys, getCredentialDetails, getCredentialSidebarPreferences, getExternalAutoProvision, getHostAccess, getHostAuthOverride, getHostPassword, getHostSidebarPreferences, getLogLevel, getPasswordLoginAllowed, getPasswordResetAllowed, getPermissionsCatalog, getRegistrationAllowed, getSecondFactorAfterExternalLogin, getSessions, getSessionTimeout, getSSHFolders, getSSHHosts, getStatusCheckSettings, getUserInfo, getUserList, getUserRoles, type LinkedAccountInfo, linkExternalToPasswordAccount, logoutUser, makeUserAdmin, type OpenTabRecord, type PermissionCatalogEntry, releaseUrlFrom, removeAdminStatus, removeRoleFromUser, renameCredentialFolder, renameFolder, reorderCredentials, reorderFolders, reorderSSHHosts, resetRecentActivity, revokeAllUserSessions, revokeHostAccess, revokeSession, type Role, saveCredentialSidebarPreferences, saveHostSidebarPreferences, setHostAuthOverride, shareFolder, shareHost, type SSHHostWithStatus, unlinkExternalFromPasswordAccount, updateCredential, updateExternalAutoProvision, updateFolderMetadata, updateHostAccess, updateLogLevel, updatePasswordLoginAllowed, updatePasswordResetAllowed, updateRegistrationAllowed, updateRole, updateSecondFactorAfterExternalLogin, updateSessionTimeout, updateSSHHost, updateStatusCheckSettings, type UserPreferences, type UserRole, } from "../main-axios";
|
|
168
|
+
export { type AccessRecord, type ActiveSessionInfo, adminCreateUser, adminCreateUserCredential, adminCreateUserHost, adminDeleteUserCredential, adminDeleteUserHost, adminExportUserData, adminGetHostPassword, adminGetUserCredentials, adminGetUserHosts, adminResetUserPassword, adminUpdateUserCredential, adminUpdateUserHost, type ApiKey, assignRoleToUser, bulkUpdateSSHHosts, changePassword, createApiKey, createCredential, type CreatedApiKey, createRole, createSSHHost, deleteAccount, deleteAllHostsInFolder, deleteApiKey, deleteCredential, deleteOpenTab, deleteRole, deleteSSHHost, deleteUser, duplicateCredential, getActiveSessions, getApiKeys, getCredentialDetails, getCredentialSidebarPreferences, getExternalAutoProvision, getHostAccess, getHostAuthOverride, getHostPassword, getHostPolicy, type HostPolicy, getHostSidebarPreferences, getHttpsSettings, type HttpsSettings, getLogLevel, getPasswordLoginAllowed, getPasswordResetAllowed, getPermissionsCatalog, getRegistrationAllowed, getSecondFactorAfterExternalLogin, getSessions, getSessionTimeout, getSSHFolders, getSSHHosts, getStatusCheckSettings, getUserInfo, getUserList, getUserRoles, type LinkedAccountInfo, linkExternalToPasswordAccount, logoutUser, makeUserAdmin, type OpenTabRecord, type PermissionCatalogEntry, releaseUrlFrom, removeAdminStatus, removeRoleFromUser, renameCredentialFolder, renameFolder, reorderCredentials, reorderFolders, reorderSSHHosts, resetRecentActivity, revokeAllUserSessions, revokeHostAccess, revokeSession, type Role, saveCredentialSidebarPreferences, saveHostSidebarPreferences, setHostAuthOverride, shareFolder, shareHost, type SSHHostWithStatus, unlinkExternalFromPasswordAccount, updateCredential, updateExternalAutoProvision, updateFolderMetadata, updateHostAccess, updateHttpsSettings, updateLogLevel, updatePasswordLoginAllowed, updatePasswordResetAllowed, updateRegistrationAllowed, updateRole, updateSecondFactorAfterExternalLogin, updateSessionTimeout, updateSSHHost, updateStatusCheckSettings, type UserPreferences, type UserRole, } from "../main-axios";
|
|
169
169
|
export { MANAGE_REQUEST_EVENT, type ManageMode, type ManageRequest, requestFromLegacyEvent, requestManage, takePendingManageRequest, } from "../manage/manage-requests";
|
|
170
170
|
export { useSshAuthEditors } from "./auth-registry";
|
|
171
171
|
export { usePluginStore } from "./plugin-store";
|
|
@@ -2,4 +2,7 @@
|
|
|
2
2
|
* Enrolment sections login methods and second factors register, such as
|
|
3
3
|
* adding a passkey or setting up an authenticator app.
|
|
4
4
|
*/
|
|
5
|
-
export declare function AuthEnrollmentSections(
|
|
5
|
+
export declare function AuthEnrollmentSections({ factorsOnly, }?: {
|
|
6
|
+
/** Only second factors, for a login policy that requires one. */
|
|
7
|
+
factorsOnly?: boolean;
|
|
8
|
+
}): import("react").JSX.Element;
|
package/dist/host/ui.js
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
getPollingEnvironmentMultiplier,
|
|
53
53
|
getPreferredHostAction,
|
|
54
54
|
getRegisteredDashboardCard,
|
|
55
|
+
getRememberDeviceAllowed,
|
|
55
56
|
getStatusClasses,
|
|
56
57
|
getSyncConflicts,
|
|
57
58
|
getSyncErrors,
|
|
@@ -173,10 +174,10 @@ import {
|
|
|
173
174
|
useThemes,
|
|
174
175
|
verifySecondFactor,
|
|
175
176
|
withProtocolSettings
|
|
176
|
-
} from "./chunk-
|
|
177
|
+
} from "./chunk-V44FCJBQ.js";
|
|
177
178
|
import {
|
|
178
179
|
usePermissions
|
|
179
|
-
} from "./chunk-
|
|
180
|
+
} from "./chunk-57DJU2ZA.js";
|
|
180
181
|
import {
|
|
181
182
|
AddButton,
|
|
182
183
|
Card,
|
|
@@ -197,7 +198,7 @@ import {
|
|
|
197
198
|
isPrerelease,
|
|
198
199
|
reportBetaFeedbackUrl,
|
|
199
200
|
reportCoreIssueUrl
|
|
200
|
-
} from "./chunk-
|
|
201
|
+
} from "./chunk-VFTB2RDP.js";
|
|
201
202
|
import {
|
|
202
203
|
Action,
|
|
203
204
|
Activity,
|
|
@@ -387,7 +388,7 @@ import {
|
|
|
387
388
|
useSurfaceKind,
|
|
388
389
|
useUiPreference,
|
|
389
390
|
useUiPreferencesContext
|
|
390
|
-
} from "./chunk-
|
|
391
|
+
} from "./chunk-2IV3VX7F.js";
|
|
391
392
|
import {
|
|
392
393
|
CREDENTIAL_ROW_FIELD_KEYS,
|
|
393
394
|
PRESETS,
|
|
@@ -450,7 +451,9 @@ import {
|
|
|
450
451
|
getHostAccess,
|
|
451
452
|
getHostAuthOverride,
|
|
452
453
|
getHostPassword,
|
|
454
|
+
getHostPolicy,
|
|
453
455
|
getHostSidebarPreferences,
|
|
456
|
+
getHttpsSettings,
|
|
454
457
|
getLogLevel,
|
|
455
458
|
getPasswordLoginAllowed,
|
|
456
459
|
getPasswordResetAllowed,
|
|
@@ -531,6 +534,7 @@ import {
|
|
|
531
534
|
updateExternalAutoProvision,
|
|
532
535
|
updateFolderMetadata,
|
|
533
536
|
updateHostAccess,
|
|
537
|
+
updateHttpsSettings,
|
|
534
538
|
updateLogLevel,
|
|
535
539
|
updatePasswordLoginAllowed,
|
|
536
540
|
updatePasswordResetAllowed,
|
|
@@ -544,7 +548,7 @@ import {
|
|
|
544
548
|
useTabs,
|
|
545
549
|
useTabsSafe,
|
|
546
550
|
verifyPasswordResetCode
|
|
547
|
-
} from "./chunk-
|
|
551
|
+
} from "./chunk-3L45NNT5.js";
|
|
548
552
|
import {
|
|
549
553
|
define_import_meta_env_default,
|
|
550
554
|
init_define_import_meta_env
|
|
@@ -4343,6 +4347,7 @@ function Auth({ onLogin }) {
|
|
|
4343
4347
|
const [username, setUsername] = useState7("");
|
|
4344
4348
|
const [password, setPassword] = useState7("");
|
|
4345
4349
|
const [confirmPassword, setConfirmPassword] = useState7("");
|
|
4350
|
+
const [rememberAllowed, setRememberAllowed] = useState7(true);
|
|
4346
4351
|
const [rememberMe, setRememberMe] = useState7(() => {
|
|
4347
4352
|
try {
|
|
4348
4353
|
return localStorage.getItem("rememberMe") === "true";
|
|
@@ -4463,6 +4468,11 @@ function Auth({ onLogin }) {
|
|
|
4463
4468
|
getPasswordResetAllowed().then((allowed) => setPasswordResetAllowed(allowed)).catch(() => setPasswordResetAllowed(false));
|
|
4464
4469
|
void startPreLoginPlugins().catch(() => {
|
|
4465
4470
|
}).finally(() => setAuthPluginsLoading(false));
|
|
4471
|
+
getRememberDeviceAllowed().then((allowed) => {
|
|
4472
|
+
setRememberAllowed(allowed);
|
|
4473
|
+
if (!allowed) setRememberMe(false);
|
|
4474
|
+
}).catch(() => {
|
|
4475
|
+
});
|
|
4466
4476
|
getLoginMethods().then((methods) => setAuthMethods(methods)).catch(() => setAuthMethods([])).finally(() => setAuthMethodsLoaded(true));
|
|
4467
4477
|
}, [localDesktopAuth]);
|
|
4468
4478
|
useEffect6(() => {
|
|
@@ -5358,7 +5368,7 @@ function Auth({ onLogin }) {
|
|
|
5358
5368
|
/* @__PURE__ */ jsx28("p", { className: "text-xs text-muted-foreground", children: view === "login" ? t3("auth.loginSubtitle", "") : view === "register" ? t3("auth.registerSubtitle", "") : t3("auth.loginWithExternalDesc") })
|
|
5359
5369
|
] }),
|
|
5360
5370
|
view === "external" && /* @__PURE__ */ jsxs20("div", { className: "flex flex-col gap-4", children: [
|
|
5361
|
-
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
5371
|
+
rememberAllowed && /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
5362
5372
|
/* @__PURE__ */ jsx28(
|
|
5363
5373
|
Checkbox,
|
|
5364
5374
|
{
|
|
@@ -5409,7 +5419,7 @@ function Auth({ onLogin }) {
|
|
|
5409
5419
|
}
|
|
5410
5420
|
) }),
|
|
5411
5421
|
/* @__PURE__ */ jsxs20("div", { className: "flex items-center justify-between", children: [
|
|
5412
|
-
/* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
5422
|
+
rememberAllowed ? /* @__PURE__ */ jsxs20("div", { className: "flex items-center gap-2", children: [
|
|
5413
5423
|
/* @__PURE__ */ jsx28(
|
|
5414
5424
|
Checkbox,
|
|
5415
5425
|
{
|
|
@@ -5427,7 +5437,7 @@ function Auth({ onLogin }) {
|
|
|
5427
5437
|
children: t3("auth.rememberMe")
|
|
5428
5438
|
}
|
|
5429
5439
|
)
|
|
5430
|
-
] }),
|
|
5440
|
+
] }) : /* @__PURE__ */ jsx28("span", {}),
|
|
5431
5441
|
passwordResetAllowed && /* @__PURE__ */ jsx28(
|
|
5432
5442
|
"button",
|
|
5433
5443
|
{
|
|
@@ -33898,9 +33908,13 @@ function SplitLayoutMenu({
|
|
|
33898
33908
|
init_define_import_meta_env();
|
|
33899
33909
|
import { useTranslation as useTranslation28 } from "react-i18next";
|
|
33900
33910
|
import { jsx as jsx57, jsxs as jsxs44 } from "react/jsx-runtime";
|
|
33901
|
-
function AuthEnrollmentSections(
|
|
33911
|
+
function AuthEnrollmentSections({
|
|
33912
|
+
factorsOnly = false
|
|
33913
|
+
} = {}) {
|
|
33902
33914
|
const { t: t3 } = useTranslation28();
|
|
33903
|
-
const
|
|
33915
|
+
const methods = useLoginMethods();
|
|
33916
|
+
const factors = useSecondFactors();
|
|
33917
|
+
const sections = [...factorsOnly ? [] : methods, ...factors].filter(
|
|
33904
33918
|
(entry) => entry.enrollment
|
|
33905
33919
|
);
|
|
33906
33920
|
if (sections.length === 0) return null;
|
|
@@ -34205,8 +34219,10 @@ export {
|
|
|
34205
34219
|
getHostDefaultsLevel,
|
|
34206
34220
|
getHostEditorSection,
|
|
34207
34221
|
getHostPassword,
|
|
34222
|
+
getHostPolicy,
|
|
34208
34223
|
getHostSidebarPreferences,
|
|
34209
34224
|
getHostTags,
|
|
34225
|
+
getHttpsSettings,
|
|
34210
34226
|
getLiveHostStatus,
|
|
34211
34227
|
getLogLevel,
|
|
34212
34228
|
getNotificationPrivateEndpoints,
|
|
@@ -34364,6 +34380,7 @@ export {
|
|
|
34364
34380
|
updateExternalAutoProvision,
|
|
34365
34381
|
updateFolderMetadata,
|
|
34366
34382
|
updateHostAccess,
|
|
34383
|
+
updateHttpsSettings,
|
|
34367
34384
|
updateLogLevel,
|
|
34368
34385
|
updatePasswordLoginAllowed,
|
|
34369
34386
|
updatePasswordResetAllowed,
|
package/dist/manifest.d.ts
CHANGED
|
@@ -20,7 +20,7 @@ export { ENV_NAME_PATTERN, MAX_ENV_DESCRIPTION_LENGTH, pluginDocsPage, pluginDoc
|
|
|
20
20
|
* "^1.2" needs 1.2 or later). engine.termix is checked against the core
|
|
21
21
|
* version by the server.
|
|
22
22
|
*/
|
|
23
|
-
export declare const PLUGIN_API_VERSION = "1.
|
|
23
|
+
export declare const PLUGIN_API_VERSION = "1.7.0";
|
|
24
24
|
/** The API major, the default engine.api for a new plugin. */
|
|
25
25
|
export declare const SUPPORTED_PLUGIN_API_VERSION: string;
|
|
26
26
|
/** Whether this build's plugin API satisfies a manifest's engine.api. */
|
package/dist/manifest.js
CHANGED
|
@@ -22,7 +22,7 @@ export { ENV_NAME_PATTERN, MAX_ENV_DESCRIPTION_LENGTH, pluginDocsPage, pluginDoc
|
|
|
22
22
|
* "^1.2" needs 1.2 or later). engine.termix is checked against the core
|
|
23
23
|
* version by the server.
|
|
24
24
|
*/
|
|
25
|
-
export const PLUGIN_API_VERSION = "1.
|
|
25
|
+
export const PLUGIN_API_VERSION = "1.7.0";
|
|
26
26
|
/** The API major, the default engine.api for a new plugin. */
|
|
27
27
|
export const SUPPORTED_PLUGIN_API_VERSION = String(semver.major(PLUGIN_API_VERSION));
|
|
28
28
|
/** Whether this build's plugin API satisfies a manifest's engine.api. */
|
package/dist/testing.d.ts
CHANGED
|
@@ -25,7 +25,7 @@ export interface FakeWsRoute {
|
|
|
25
25
|
import type { PluginLoginMethod, PluginVerifiedIdentity, PluginSecondFactor, PluginSshAuthProvider, PluginSshConnectOptions, PluginSshHost, PluginHostSummary, PluginShareableUser, PluginShareableRole, PluginSshKeyCredential, PluginSshKeyCredentialInput } from "./backend.js";
|
|
26
26
|
import type { SyncEntityRegistration } from "./backend.js";
|
|
27
27
|
import type { PluginDatabase } from "./backend.js";
|
|
28
|
-
import type { PluginExternalClientRequest, PluginProtocolTarget, PluginHostProbeTarget, PluginHostStatusEntry, PluginActivityProvider, PluginHostStatusProvider, PluginNotification, PluginNotificationChannel, PluginNotifyHub, PluginFetchInit, PluginBinarySpec, PluginTlsCertificateInfo, PluginTlsStatus, PluginSummary, PluginKeyboardInteractiveHandler, PluginProcessHandle, PluginProcessOptions } from "./backend.js";
|
|
28
|
+
import type { PluginExternalClientRequest, PluginProtocolTarget, PluginHostProbeTarget, PluginHostStatusEntry, PluginActivityProvider, PluginHostLockProvider, PluginAuthPolicyProvider, PluginCredentialSummary, PluginHostShareGrant, PluginHostStatusProvider, PluginNotification, PluginNotificationChannel, PluginNotifyHub, PluginFetchInit, PluginBinarySpec, PluginTlsCertificateInfo, PluginTlsStatus, PluginSummary, PluginKeyboardInteractiveHandler, PluginProcessHandle, PluginProcessOptions } from "./backend.js";
|
|
29
29
|
/** A timer registered through ctx.schedule, run by hand with runScheduled. */
|
|
30
30
|
export interface FakeScheduledJob {
|
|
31
31
|
kind: "every" | "after";
|
|
@@ -75,6 +75,8 @@ export interface FakeContextOptions {
|
|
|
75
75
|
protocolTargets?: Record<string, PluginProtocolTarget>;
|
|
76
76
|
/** What ctx.credentials.listSshKeys answers. */
|
|
77
77
|
sshKeyCredentials?: PluginSshKeyCredential[];
|
|
78
|
+
/** What ctx.credentials.list answers. */
|
|
79
|
+
credentialSummaries?: PluginCredentialSummary[];
|
|
78
80
|
/** What ctx.desktop.available() answers. Defaults to false. */
|
|
79
81
|
desktopAvailable?: boolean;
|
|
80
82
|
/** What ctx.desktop.local is. Calls throw when this is not given. */
|
|
@@ -122,6 +124,11 @@ export interface FakeContextOptions {
|
|
|
122
124
|
baseUrl?: string;
|
|
123
125
|
/** What ctx.auth.countLinkedUsers answers, by provider. Defaults to 0. */
|
|
124
126
|
linkedUsers?: Record<string, number>;
|
|
127
|
+
/** Second factors from other plugins that ctx.auth.listSecondFactors adds. */
|
|
128
|
+
secondFactors?: {
|
|
129
|
+
id: string;
|
|
130
|
+
pluginId: string;
|
|
131
|
+
}[];
|
|
125
132
|
/** Failures before ctx.auth.loginRateLimit locks a key. Defaults to 5. */
|
|
126
133
|
loginAttemptLimit?: number;
|
|
127
134
|
/** What ctx.users.getLocale answers, by user id. A missing user answers null. */
|
|
@@ -167,6 +174,8 @@ export interface FakeAuthRegistrations {
|
|
|
167
174
|
purpose: string;
|
|
168
175
|
ttlSeconds?: number;
|
|
169
176
|
}>;
|
|
177
|
+
/** Providers passed to ctx.auth.policy.provide that are still registered. */
|
|
178
|
+
policies: PluginAuthPolicyProvider[];
|
|
170
179
|
}
|
|
171
180
|
export interface FakePluginContext {
|
|
172
181
|
ctx: PluginContext;
|
|
@@ -180,6 +189,8 @@ export interface FakePluginContext {
|
|
|
180
189
|
hostStatusProviders: PluginHostStatusProvider[];
|
|
181
190
|
/** Every provider passed to ctx.hosts.activity.provide that is still registered. */
|
|
182
191
|
activityProviders: PluginActivityProvider[];
|
|
192
|
+
/** Every provider passed to ctx.hosts.locks.provide that is still registered. */
|
|
193
|
+
hostLockProviders: PluginHostLockProvider[];
|
|
183
194
|
/** Topics emitted through ctx.events.emit, in order. */
|
|
184
195
|
emitted: Array<{
|
|
185
196
|
topic: string;
|
|
@@ -215,6 +226,11 @@ export interface FakePluginContext {
|
|
|
215
226
|
permissionLevel: string;
|
|
216
227
|
durationHours?: number;
|
|
217
228
|
}>;
|
|
229
|
+
/** Every ctx.hosts.setShares call, in order. */
|
|
230
|
+
hostShareSets: Array<{
|
|
231
|
+
hostId: number;
|
|
232
|
+
grants: PluginHostShareGrant[];
|
|
233
|
+
}>;
|
|
218
234
|
/** Everything registered through ctx.auth. */
|
|
219
235
|
auth: FakeAuthRegistrations;
|
|
220
236
|
/** Every ctx.desktop.openIsolatedWindow call, in order. */
|
|
@@ -364,6 +380,8 @@ export interface MockContextOptions {
|
|
|
364
380
|
protocolTargets?: Record<string, PluginProtocolTarget>;
|
|
365
381
|
/** What ctx.credentials.listSshKeys serves. */
|
|
366
382
|
sshKeyCredentials?: PluginSshKeyCredential[];
|
|
383
|
+
/** What ctx.credentials.list serves. */
|
|
384
|
+
credentialSummaries?: PluginCredentialSummary[];
|
|
367
385
|
/** What ctx.desktop.available() answers. */
|
|
368
386
|
desktopAvailable?: boolean;
|
|
369
387
|
/** What ctx.desktop.local is. See FakeContextOptions. */
|
|
@@ -402,6 +420,8 @@ export interface MockContextOptions {
|
|
|
402
420
|
/** See FakeContextOptions. */
|
|
403
421
|
loginAttemptLimit?: number;
|
|
404
422
|
/** See FakeContextOptions. */
|
|
423
|
+
secondFactors?: FakeContextOptions["secondFactors"];
|
|
424
|
+
/** See FakeContextOptions. */
|
|
405
425
|
locales?: Record<string, string>;
|
|
406
426
|
}
|
|
407
427
|
export interface MockPluginContext extends FakePluginContext {
|
package/dist/testing.js
CHANGED
|
@@ -100,7 +100,7 @@ function emptyHostRecord(id, userId) {
|
|
|
100
100
|
};
|
|
101
101
|
}
|
|
102
102
|
function applyHostInput(record, input) {
|
|
103
|
-
const { tags, pluginSettings: _settings, forceKeyboardInteractive: _fki, ...rest } = input;
|
|
103
|
+
const { tags, pluginSettings: _settings, pluginEnable: _pluginEnable, forceKeyboardInteractive: _fki, password: _password, key: _key, keyPassword: _keyPassword, keyType: _keyType, sudoPassword: _sudoPassword, protocolAuth: _protocolAuth, ...rest } = input;
|
|
104
104
|
const next = { ...record };
|
|
105
105
|
for (const [key, value] of Object.entries(rest)) {
|
|
106
106
|
if (value !== undefined)
|
|
@@ -126,6 +126,7 @@ export function createFakeContext(options = {}) {
|
|
|
126
126
|
const emitted = [];
|
|
127
127
|
const hostStatusProviders = [];
|
|
128
128
|
const activityProviders = [];
|
|
129
|
+
const hostLockProviders = [];
|
|
129
130
|
const kv = new Map();
|
|
130
131
|
const tables = [];
|
|
131
132
|
const syncEntities = [];
|
|
@@ -138,6 +139,7 @@ export function createFakeContext(options = {}) {
|
|
|
138
139
|
const settingsValidators = new Set();
|
|
139
140
|
const sshConnections = [];
|
|
140
141
|
const hostShares = [];
|
|
142
|
+
const hostShareSets = [];
|
|
141
143
|
const desktopWindows = [];
|
|
142
144
|
const externalClientLaunches = [];
|
|
143
145
|
const credentialReads = [];
|
|
@@ -198,6 +200,7 @@ export function createFakeContext(options = {}) {
|
|
|
198
200
|
revokedSessions: [],
|
|
199
201
|
loginFailures: new Map(),
|
|
200
202
|
userTokens: [],
|
|
203
|
+
policies: [],
|
|
201
204
|
};
|
|
202
205
|
const sshClient = options.sshClient ?? {};
|
|
203
206
|
let actor = options.actor;
|
|
@@ -526,6 +529,10 @@ export function createFakeContext(options = {}) {
|
|
|
526
529
|
hostShares.push({ hostId, targets, permissionLevel, durationHours });
|
|
527
530
|
return { hostId, shared: true };
|
|
528
531
|
},
|
|
532
|
+
setShares: async (hostId, grants) => {
|
|
533
|
+
hostShareSets.push({ hostId, grants: [...grants] });
|
|
534
|
+
return { hostId, shared: true };
|
|
535
|
+
},
|
|
529
536
|
listUsers: async () => options.shareableUsers ?? [],
|
|
530
537
|
listRoles: async () => options.shareableRoles ?? [],
|
|
531
538
|
trackSession: (hostId) => {
|
|
@@ -555,6 +562,16 @@ export function createFakeContext(options = {}) {
|
|
|
555
562
|
};
|
|
556
563
|
},
|
|
557
564
|
},
|
|
565
|
+
locks: {
|
|
566
|
+
provide: (provider) => {
|
|
567
|
+
hostLockProviders.push(provider);
|
|
568
|
+
return () => {
|
|
569
|
+
const at = hostLockProviders.indexOf(provider);
|
|
570
|
+
if (at >= 0)
|
|
571
|
+
hostLockProviders.splice(at, 1);
|
|
572
|
+
};
|
|
573
|
+
},
|
|
574
|
+
},
|
|
558
575
|
status: {
|
|
559
576
|
provide: (provider) => {
|
|
560
577
|
hostStatusProviders.push(provider);
|
|
@@ -761,6 +778,23 @@ export function createFakeContext(options = {}) {
|
|
|
761
778
|
expiresAt: new Date(Date.now() + ttl * 1000).toISOString(),
|
|
762
779
|
};
|
|
763
780
|
},
|
|
781
|
+
policy: {
|
|
782
|
+
provide: (provider) => {
|
|
783
|
+
auth.policies.push(provider);
|
|
784
|
+
return () => {
|
|
785
|
+
const at = auth.policies.indexOf(provider);
|
|
786
|
+
if (at >= 0)
|
|
787
|
+
auth.policies.splice(at, 1);
|
|
788
|
+
};
|
|
789
|
+
},
|
|
790
|
+
},
|
|
791
|
+
listSecondFactors: () => [
|
|
792
|
+
...auth.secondFactors.map((factor) => ({
|
|
793
|
+
id: factor.id,
|
|
794
|
+
pluginId,
|
|
795
|
+
})),
|
|
796
|
+
...(options.secondFactors ?? []),
|
|
797
|
+
],
|
|
764
798
|
},
|
|
765
799
|
users: {
|
|
766
800
|
getLocale: async (userId) => options.locales?.[userId] ?? null,
|
|
@@ -778,6 +812,7 @@ export function createFakeContext(options = {}) {
|
|
|
778
812
|
local: options.desktopLocal ?? unavailableDesktopLocal(),
|
|
779
813
|
},
|
|
780
814
|
credentials: {
|
|
815
|
+
list: async () => [...(options.credentialSummaries ?? [])],
|
|
781
816
|
listSshKeys: async () => [
|
|
782
817
|
...(options.sshKeyCredentials ?? []),
|
|
783
818
|
...createdSshKeys.map((key) => ({
|
|
@@ -951,6 +986,7 @@ export function createFakeContext(options = {}) {
|
|
|
951
986
|
disposals,
|
|
952
987
|
hostStatusProviders,
|
|
953
988
|
activityProviders,
|
|
989
|
+
hostLockProviders,
|
|
954
990
|
emitted,
|
|
955
991
|
kv,
|
|
956
992
|
tables,
|
|
@@ -961,6 +997,7 @@ export function createFakeContext(options = {}) {
|
|
|
961
997
|
coreSettings,
|
|
962
998
|
sshConnections,
|
|
963
999
|
hostShares,
|
|
1000
|
+
hostShareSets,
|
|
964
1001
|
auth,
|
|
965
1002
|
desktopWindows,
|
|
966
1003
|
externalClientLaunches,
|
|
@@ -1085,6 +1122,7 @@ export function createMockCtx(options = {}) {
|
|
|
1085
1122
|
services: options.services,
|
|
1086
1123
|
protocolTargets: options.protocolTargets,
|
|
1087
1124
|
sshKeyCredentials: options.sshKeyCredentials,
|
|
1125
|
+
credentialSummaries: options.credentialSummaries,
|
|
1088
1126
|
desktopAvailable: options.desktopAvailable,
|
|
1089
1127
|
desktopLocal: options.desktopLocal,
|
|
1090
1128
|
auditEntries: options.auditEntries,
|
|
@@ -1102,6 +1140,7 @@ export function createMockCtx(options = {}) {
|
|
|
1102
1140
|
baseUrl: options.baseUrl,
|
|
1103
1141
|
linkedUsers: options.linkedUsers,
|
|
1104
1142
|
loginAttemptLimit: options.loginAttemptLimit,
|
|
1143
|
+
secondFactors: options.secondFactors,
|
|
1105
1144
|
locales: options.locales,
|
|
1106
1145
|
manifest: {
|
|
1107
1146
|
capabilities: options.capabilities ?? [],
|
|
@@ -1268,6 +1307,10 @@ export function createMockCtx(options = {}) {
|
|
|
1268
1307
|
require("hosts:write");
|
|
1269
1308
|
return ctx.hosts.share(hostId, targets, permissionLevel, durationHours);
|
|
1270
1309
|
},
|
|
1310
|
+
setShares: async (hostId, grants) => {
|
|
1311
|
+
require("hosts:write");
|
|
1312
|
+
return ctx.hosts.setShares(hostId, grants);
|
|
1313
|
+
},
|
|
1271
1314
|
listUsers: async () => {
|
|
1272
1315
|
require("hosts:write");
|
|
1273
1316
|
return ctx.hosts.listUsers();
|
|
@@ -1298,6 +1341,12 @@ export function createMockCtx(options = {}) {
|
|
|
1298
1341
|
return ctx.hosts.activity.provide(provider);
|
|
1299
1342
|
},
|
|
1300
1343
|
},
|
|
1344
|
+
locks: {
|
|
1345
|
+
provide: (provider) => {
|
|
1346
|
+
require("hosts:lock");
|
|
1347
|
+
return ctx.hosts.locks.provide(provider);
|
|
1348
|
+
},
|
|
1349
|
+
},
|
|
1301
1350
|
status: {
|
|
1302
1351
|
provide: (provider) => {
|
|
1303
1352
|
require("hosts:read");
|
|
@@ -1423,6 +1472,13 @@ export function createMockCtx(options = {}) {
|
|
|
1423
1472
|
require("users:impersonate");
|
|
1424
1473
|
return ctx.auth.issueUserToken(userId, tokenOptions);
|
|
1425
1474
|
},
|
|
1475
|
+
policy: {
|
|
1476
|
+
provide: (provider) => {
|
|
1477
|
+
require("auth:policy");
|
|
1478
|
+
return ctx.auth.policy.provide(provider);
|
|
1479
|
+
},
|
|
1480
|
+
},
|
|
1481
|
+
listSecondFactors: () => ctx.auth.listSecondFactors(),
|
|
1426
1482
|
},
|
|
1427
1483
|
users: {
|
|
1428
1484
|
getLocale: async (userId) => {
|
|
@@ -1458,6 +1514,10 @@ export function createMockCtx(options = {}) {
|
|
|
1458
1514
|
local: gateDesktopLocal(ctx.desktop.local, () => require("desktop:local")),
|
|
1459
1515
|
},
|
|
1460
1516
|
credentials: {
|
|
1517
|
+
list: async () => {
|
|
1518
|
+
require("credentials:use");
|
|
1519
|
+
return ctx.credentials.list();
|
|
1520
|
+
},
|
|
1461
1521
|
listSshKeys: async () => {
|
|
1462
1522
|
require("credentials:use");
|
|
1463
1523
|
return ctx.credentials.listSshKeys();
|
package/dist/vitest-preset.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Two projects: tests/backend runs in node, tests/frontend in jsdom.
|
|
10
10
|
*/
|
|
11
|
+
import { createRequire } from "node:module";
|
|
11
12
|
import fs from "node:fs";
|
|
12
13
|
import path from "node:path";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
@@ -84,6 +85,28 @@ export function pluginVitestConfig(configUrl, options = {}) {
|
|
|
84
85
|
// Vite must be allowed to read it, and React must come from the plugin.
|
|
85
86
|
const sdkDir = fs.realpathSync(fileURLToPath(new URL("..", import.meta.url)));
|
|
86
87
|
const server = { fs: { allow: [pluginDir, sdkDir] } };
|
|
88
|
+
// dedupe alone is not enough: react-dom or the testing library loaded from
|
|
89
|
+
// the linked SDK's own node_modules pull in a second React, and hooks then
|
|
90
|
+
// see no renderer.
|
|
91
|
+
if (!sdkDir.startsWith(pluginDir)) {
|
|
92
|
+
const pluginRequire = createRequire(path.join(pluginDir, "package.json"));
|
|
93
|
+
for (const name of [
|
|
94
|
+
"react",
|
|
95
|
+
"react-dom",
|
|
96
|
+
"@testing-library/react",
|
|
97
|
+
"react-i18next",
|
|
98
|
+
"i18next",
|
|
99
|
+
]) {
|
|
100
|
+
if (alias[name])
|
|
101
|
+
continue;
|
|
102
|
+
try {
|
|
103
|
+
alias[name] = path.dirname(pluginRequire.resolve(`${name}/package.json`));
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Not installed in the plugin: leave it to normal resolution.
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
87
110
|
const resolve = {
|
|
88
111
|
alias,
|
|
89
112
|
dedupe: ["react", "react-dom", "react-i18next", "i18next"],
|
package/package.json
CHANGED
|
@@ -151,6 +151,7 @@
|
|
|
151
151
|
"process:spawn",
|
|
152
152
|
"users:write",
|
|
153
153
|
"auth:provide",
|
|
154
|
+
"auth:policy",
|
|
154
155
|
"system:tls",
|
|
155
156
|
"device:serial",
|
|
156
157
|
"hosts:write",
|
|
@@ -167,6 +168,7 @@
|
|
|
167
168
|
"network:probe",
|
|
168
169
|
"hosts:status-provide",
|
|
169
170
|
"hosts:activity-provide",
|
|
171
|
+
"hosts:lock",
|
|
170
172
|
"desktop:window",
|
|
171
173
|
"desktop:local",
|
|
172
174
|
"users:impersonate",
|