dsh-mobile 0.1.0-alpha.1

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.
@@ -0,0 +1,409 @@
1
+ import z from "@deepseek-ai/schemastery";
2
+ import { WebRoute } from "@deepseek-ai/dsh-host-webserver";
3
+ import { Context } from "@deepseek-ai/cordis";
4
+ //#region src/storage.d.ts
5
+ /** Persistent record containing only a digest of the long-lived device credential. */
6
+ interface StoredDevice {
7
+ readonly id: string;
8
+ readonly label: string;
9
+ readonly tokenDigest: string;
10
+ readonly createdAt: number;
11
+ readonly expiresAt: number;
12
+ readonly lastSeenAt: number;
13
+ readonly revokedAt?: number;
14
+ }
15
+ /** Versioned device state. Raw device and Session credentials are never members. */
16
+ interface DeviceSnapshot {
17
+ readonly version: 1;
18
+ readonly devices: readonly StoredDevice[];
19
+ }
20
+ /** Persistence seam for device-token digests and revocation metadata. */
21
+ interface DeviceStore {
22
+ load(): Promise<DeviceSnapshot>;
23
+ save(snapshot: DeviceSnapshot): Promise<void>;
24
+ }
25
+ /** Validate durable data before it can authorize a device. */
26
+ declare function parseDeviceSnapshot(value: unknown, maximumDevices?: number): DeviceSnapshot;
27
+ /** Atomic JSON implementation with symlink refusal and owner-only file creation. */
28
+ declare class JsonDeviceStore implements DeviceStore {
29
+ private readonly file;
30
+ private readonly maximumDevices;
31
+ constructor(file: string, maximumDevices?: number);
32
+ load(): Promise<DeviceSnapshot>;
33
+ save(snapshot: DeviceSnapshot): Promise<void>;
34
+ }
35
+ /** In-memory store useful for embedding and deterministic tests. */
36
+ declare class MemoryDeviceStore implements DeviceStore {
37
+ private snapshot;
38
+ constructor(initial?: DeviceSnapshot);
39
+ load(): Promise<DeviceSnapshot>;
40
+ save(snapshot: DeviceSnapshot): Promise<void>;
41
+ /** Return a defensive copy for assertions or administrative export. */
42
+ inspect(): DeviceSnapshot;
43
+ }
44
+ //#endregion
45
+ //#region src/access.d.ts
46
+ /** Stable error categories converted to deliberately terse HTTP responses. */
47
+ declare class AccessError extends Error {
48
+ readonly status: number;
49
+ readonly code: string;
50
+ constructor(status: number, code: string);
51
+ }
52
+ /** Resource and lifetime controls for device authentication. */
53
+ interface AccessControllerOptions {
54
+ readonly pairingTtlMs: number;
55
+ readonly deviceTtlMs: number;
56
+ readonly sessionTtlMs: number;
57
+ readonly maxDevices: number;
58
+ readonly maxSessions: number;
59
+ readonly rateLimitWindowMs: number;
60
+ readonly maxPairingAttempts: number;
61
+ readonly maxRateLimitKeys: number;
62
+ readonly now?: () => number;
63
+ }
64
+ /** Values issued once after pairing; only digests survive the response. */
65
+ interface PairingResult {
66
+ readonly deviceId: string;
67
+ readonly deviceToken: string;
68
+ readonly deviceExpiresAt: number;
69
+ readonly sessionToken: string;
70
+ readonly csrfToken: string;
71
+ readonly sessionExpiresAt: number;
72
+ }
73
+ /** Values issued after renewal with the persistent HttpOnly device Cookie. */
74
+ interface RenewalResult {
75
+ readonly deviceId: string;
76
+ readonly sessionToken: string;
77
+ readonly csrfToken: string;
78
+ readonly sessionExpiresAt: number;
79
+ }
80
+ /** Authenticated Session identity retained only inside the gateway. */
81
+ interface SessionAuthorization {
82
+ readonly sessionKey: string;
83
+ readonly deviceId: string;
84
+ readonly expiresAt: number;
85
+ }
86
+ /** Safe device metadata returned by the loopback administration API. */
87
+ interface DeviceSummary {
88
+ readonly id: string;
89
+ readonly label: string;
90
+ readonly createdAt: number;
91
+ readonly expiresAt: number;
92
+ readonly lastSeenAt: number;
93
+ readonly revokedAt?: number;
94
+ }
95
+ /** Fixed-window limiter whose attacker-controlled key table is itself bounded. */
96
+ declare class BoundedRateLimiter {
97
+ private readonly limit;
98
+ private readonly windowMs;
99
+ private readonly maximumKeys;
100
+ private readonly buckets;
101
+ constructor(limit: number, windowMs: number, maximumKeys: number);
102
+ /** Consume one attempt; unknown keys fail closed when the bounded table is full. */
103
+ take(key: string, now: number): boolean;
104
+ /** Current table size, exposed for bounded-state assertions. */
105
+ get size(): number;
106
+ }
107
+ /** Pairing, persistent-device, short-Session, revocation, and CSRF state machine. */
108
+ declare class AccessController {
109
+ private readonly store;
110
+ private readonly options;
111
+ private readonly now;
112
+ private readonly pairLimiter;
113
+ private devices;
114
+ private pairingWindow;
115
+ private readonly sessions;
116
+ private readonly sessionEndedListeners;
117
+ private mutation;
118
+ private initialized;
119
+ private closing;
120
+ private closeTask;
121
+ constructor(store: DeviceStore, options: AccessControllerOptions);
122
+ /** Load and validate digest-only durable state before accepting traffic. */
123
+ initialize(): Promise<void>;
124
+ private requireInitialized;
125
+ private exclusive;
126
+ private snapshot;
127
+ private emitSessionEnded;
128
+ private removeSession;
129
+ private pruneSessions;
130
+ private createSession;
131
+ /** Open one short pairing window and return its one-time secret to a loopback caller only. */
132
+ openPairing(requestedTtlMs?: number): Promise<{
133
+ token: string;
134
+ expiresAt: number;
135
+ }>;
136
+ /** Consume the pairing window exactly once and persist only the device-token digest. */
137
+ pair(sourceKey: string, token: string, label?: string): Promise<PairingResult>;
138
+ /** Exchange a valid persistent device credential for a new short Session. */
139
+ renew(deviceToken: string): Promise<RenewalResult>;
140
+ /** Resolve a short Session Cookie without revealing whether device or Session failed. */
141
+ authorizeSession(sessionToken: string): SessionAuthorization;
142
+ /** Require the Session-bound anti-CSRF value for an authenticated mutation. */
143
+ assertCsrf(authorization: SessionAuthorization, csrfToken: string | undefined): void;
144
+ /** End one short Session and notify the gateway to abort its attached work. */
145
+ logout(authorization: SessionAuthorization): void;
146
+ /** Persist revocation, then end every Session owned by that device. */
147
+ revokeDevice(deviceId: string): Promise<boolean>;
148
+ /** Remove every persistent credential and terminate every active Session. */
149
+ resetDevices(): Promise<void>;
150
+ /** Safe metadata for the loopback administration surface. */
151
+ listDevices(): readonly DeviceSummary[];
152
+ /** Pairing status without exposing the one-time secret. */
153
+ pairingStatus(): {
154
+ open: boolean;
155
+ expiresAt?: number;
156
+ };
157
+ /** Subscribe gateway resources to Session logout, expiry, eviction, and device revocation. */
158
+ onSessionEnded(listener: (authorization: SessionAuthorization) => void): () => void;
159
+ /** Stop new operations, drain durable mutations, then clear volatile credentials. */
160
+ close(): Promise<void>;
161
+ private finishClose;
162
+ /** Bounded volatile-state metrics for tests and local status. */
163
+ metrics(): {
164
+ sessions: number;
165
+ rateLimitKeys: number;
166
+ };
167
+ }
168
+ //#endregion
169
+ //#region src/network.d.ts
170
+ /** A parsed IP network used to authorize directly connected clients. */
171
+ interface ParsedCidr {
172
+ readonly bits: 32 | 128;
173
+ readonly network: bigint;
174
+ readonly prefix: number;
175
+ readonly source: string;
176
+ }
177
+ /** A normalized public authority. A missing port is filled from the bound listener. */
178
+ interface AuthoritySpec {
179
+ readonly hostname: string;
180
+ readonly port?: number;
181
+ }
182
+ /** Parse and canonicalize one IPv4 or IPv6 CIDR. */
183
+ declare function parseCidr(source: string): ParsedCidr;
184
+ /** Whether a directly connected socket address belongs to at least one allowed CIDR. */
185
+ declare function addressAllowed(address: string | undefined, cidrs: readonly ParsedCidr[]): boolean;
186
+ /** Whether an IP literal is loopback and therefore eligible for HTTP-only development. */
187
+ declare function isLoopbackAddress(address: string): boolean;
188
+ /** Parse a bare host or host:port authority without accepting URL components. */
189
+ declare function parseAuthority(source: string): AuthoritySpec;
190
+ /** Resolve an authority against the actual listener port. */
191
+ declare function resolveAuthority(spec: AuthoritySpec, listenerPort: number): string;
192
+ /** Exact Host/Origin/CIDR policy for the directly exposed listener. */
193
+ declare class RequestTrustPolicy {
194
+ readonly cidrs: readonly ParsedCidr[];
195
+ readonly authorities: ReadonlySet<string>;
196
+ readonly origins: ReadonlySet<string>;
197
+ private readonly scheme;
198
+ constructor(specs: readonly AuthoritySpec[], listenerPort: number, cidrs: readonly ParsedCidr[], tls: boolean);
199
+ /** Validate the exact Host header after WHATWG authority normalization. */
200
+ acceptsHost(header: string | undefined): boolean;
201
+ /** Return the canonical accepted Host authority, otherwise undefined. */
202
+ canonicalHost(header: string | undefined): string | undefined;
203
+ /** Validate an exact same-scheme browser Origin. */
204
+ acceptsOrigin(header: string | undefined): boolean;
205
+ /** Return the canonical accepted Origin, otherwise undefined. */
206
+ canonicalOrigin(header: string | undefined): string | undefined;
207
+ }
208
+ //#endregion
209
+ //#region src/config.d.ts
210
+ /** TLS source accepted by the LAN listener. */
211
+ interface ProvidedTlsConfig {
212
+ readonly mode: 'provided';
213
+ /** PEM server leaf followed by any intermediate certificate chain. */
214
+ readonly certFile: string;
215
+ readonly keyFile: string;
216
+ /** Optional PEM intermediates appended after the chain in `certFile`; roots are rejected. */
217
+ readonly caFile?: string;
218
+ }
219
+ /** HTTP is available only for an explicitly loopback-bound listener. */
220
+ interface DisabledTlsConfig {
221
+ readonly mode: 'disabled';
222
+ }
223
+ type TlsConfig = ProvidedTlsConfig | DisabledTlsConfig;
224
+ /** Operator-facing plugin configuration. */
225
+ interface PluginConfig {
226
+ /** Optional setup JSON written by the packaged CLI. */
227
+ setupFile?: string;
228
+ /** Preferred HTTPS origin used to derive the public authority and listener port. */
229
+ publicOrigin?: string;
230
+ listenHost?: string;
231
+ listenPort?: number;
232
+ upstreamOrigin?: string;
233
+ publicAuthorities?: string[];
234
+ allowedCidrs?: string[];
235
+ stateFile: string;
236
+ /** Internal persisted on/off preference managed by the DSH plugin card. */
237
+ controlFile: string;
238
+ /** Optional user stylesheet served to both the desktop and authenticated mobile UI. */
239
+ customCssFile?: string;
240
+ /** First-run state used only while the control file does not exist. */
241
+ initiallyEnabled: boolean;
242
+ tls?: {
243
+ mode?: 'provided' | 'disabled';
244
+ certFile?: string;
245
+ keyFile?: string;
246
+ caFile?: string;
247
+ };
248
+ pairingTtlMs?: number;
249
+ deviceTtlMs?: number;
250
+ sessionTtlMs?: number;
251
+ maxDevices?: number;
252
+ maxSessions?: number;
253
+ maxConnections?: number;
254
+ maxActiveRequests?: number;
255
+ maxWebSockets?: number;
256
+ maxBodyBytes?: number;
257
+ upstreamTimeoutMs?: number;
258
+ rateLimitWindowMs?: number;
259
+ maxPairingAttempts?: number;
260
+ maxRateLimitKeys?: number;
261
+ }
262
+ /** Resolved, validated security and resource limits. */
263
+ interface ResolvedGatewayConfig {
264
+ readonly listenHost: string;
265
+ readonly listenPort: number;
266
+ readonly upstreamOrigin: URL;
267
+ readonly authorities: readonly AuthoritySpec[];
268
+ readonly allowedCidrs: readonly ParsedCidr[];
269
+ readonly stateFile: string;
270
+ readonly customCssFile: string;
271
+ readonly tls: TlsConfig;
272
+ readonly pairingTtlMs: number;
273
+ readonly deviceTtlMs: number;
274
+ readonly sessionTtlMs: number;
275
+ readonly maxDevices: number;
276
+ readonly maxSessions: number;
277
+ readonly maxConnections: number;
278
+ readonly maxActiveRequests: number;
279
+ readonly maxWebSockets: number;
280
+ readonly maxBodyBytes: number;
281
+ readonly upstreamTimeoutMs: number;
282
+ readonly rateLimitWindowMs: number;
283
+ readonly maxPairingAttempts: number;
284
+ readonly maxRateLimitKeys: number;
285
+ }
286
+ /** Loader-facing defaults; {@link parseGatewayConfig} enforces cross-field security rules. */
287
+ declare const Config: z<PluginConfig>;
288
+ /** Resolve the hidden runtime-control file independently from gateway configuration. */
289
+ declare function parseControlFile(value: unknown): string;
290
+ /** Parse configuration and reject unsafe topology, credential, and resource combinations. */
291
+ declare function parseGatewayConfig(raw: unknown): ResolvedGatewayConfig;
292
+ //#endregion
293
+ //#region src/control.d.ts
294
+ /** Versioned durable preference for the resident mobile-access runtime. */
295
+ interface MobileAccessControlState {
296
+ readonly version: 1;
297
+ readonly enabled: boolean;
298
+ }
299
+ /** Persistence seam for the runtime preference. */
300
+ interface MobileAccessControlStore {
301
+ load(): Promise<MobileAccessControlState>;
302
+ save(state: MobileAccessControlState): Promise<void>;
303
+ }
304
+ /** One started gateway runtime owned by the controller. */
305
+ interface MobileAccessRuntime {
306
+ close(): Promise<void>;
307
+ }
308
+ /** Validate control state loaded across the filesystem boundary. */
309
+ declare function parseMobileAccessControlState(value: unknown): MobileAccessControlState;
310
+ /** Atomic JSON store whose absent-file state comes from the installation-time default. */
311
+ declare class JsonMobileAccessControlStore implements MobileAccessControlStore {
312
+ private readonly file;
313
+ private readonly initiallyEnabled;
314
+ constructor(file: string, initiallyEnabled: boolean);
315
+ load(): Promise<MobileAccessControlState>;
316
+ save(state: MobileAccessControlState): Promise<void>;
317
+ }
318
+ /** Serialized persistent lifecycle for the gateway behind the always-loaded Cordis entry. */
319
+ declare class MobileAccessGatewayController {
320
+ private readonly store;
321
+ private readonly startRuntime;
322
+ private runtime;
323
+ private initialized;
324
+ private closing;
325
+ private queue;
326
+ private closeTask;
327
+ constructor(store: MobileAccessControlStore, startRuntime: () => Promise<MobileAccessRuntime>);
328
+ /** Load the durable preference and start the first runtime when enabled. */
329
+ initialize(): Promise<void>;
330
+ /** Return the committed in-process runtime state. */
331
+ isRunning(): boolean;
332
+ /** Start or stop the runtime and persist only a successfully committed transition. */
333
+ setRunning(running: boolean): Promise<void>;
334
+ /** Stop the runtime after earlier transitions without changing the restart preference. */
335
+ close(): Promise<void>;
336
+ private enable;
337
+ private disable;
338
+ private enqueue;
339
+ }
340
+ //#endregion
341
+ //#region src/gateway.d.ts
342
+ /** Authenticated TLS edge in front of the ordinary loopback-only DSH Web server. */
343
+ declare class MobileAccessGateway {
344
+ readonly config: ResolvedGatewayConfig;
345
+ readonly access: AccessController;
346
+ private readonly tlsEnabled;
347
+ private policy;
348
+ private server;
349
+ private listenerPort;
350
+ private readonly connectedSockets;
351
+ private readonly activeRequests;
352
+ private readonly activeWebSockets;
353
+ private nextOperationId;
354
+ private closing;
355
+ private started;
356
+ private closeTask;
357
+ private readonly removeSessionListener;
358
+ private readonly renewLimiter;
359
+ constructor(config: ResolvedGatewayConfig, store: DeviceStore);
360
+ /** Initialize durable state, validate TLS, and bind the externally reachable listener. */
361
+ start(): Promise<void>;
362
+ private closeFailedStart;
363
+ /** Actual bound address, available after start and safe for loopback status output. */
364
+ address(): {
365
+ host: string;
366
+ port: number;
367
+ origin: string;
368
+ };
369
+ private requirePolicy;
370
+ private authorize;
371
+ private requireCsrf;
372
+ private setSessionCookies;
373
+ private handlePair;
374
+ private handleRenew;
375
+ private handleLogout;
376
+ private handleExternalRequest;
377
+ private allocateRequest;
378
+ private proxyHttp;
379
+ private abortSessionResources;
380
+ private readUpgradeResponse;
381
+ private handleUpgrade;
382
+ /** Loopback-only DSH WebServer route for opening pairing and managing devices. */
383
+ localAdminRoute(): WebRoute;
384
+ /** Close listeners and abort all accepted work before resolving teardown. */
385
+ close(): Promise<void>;
386
+ private performClose;
387
+ /** Safe metadata helper for direct loopback integrations. */
388
+ devices(): readonly DeviceSummary[];
389
+ }
390
+ //#endregion
391
+ //#region src/http-security.d.ts
392
+ declare const DEVICE_COOKIE = "dsh_ma_device";
393
+ declare const SESSION_COOKIE = "dsh_ma_session";
394
+ declare const CSRF_COOKIE = "dsh_ma_csrf";
395
+ declare const CSRF_HEADER = "x-dsh-mobile-csrf";
396
+ declare const LOCAL_ADMIN_PREFIX = "/api/mobile-access";
397
+ declare const AUTH_PREFIX = "/mobile-access";
398
+ declare const WS_PATHS: Set<string>;
399
+ //#endregion
400
+ //#region src/plugin.d.ts
401
+ /** Stable Cordis plugin name. */
402
+ declare const name = "dsh-mobile";
403
+ /** The stock WebServer is the only DSH Host service this plugin requires. */
404
+ declare const inject: string[];
405
+ /** Mount the resident control route and its optional authenticated LAN gateway. */
406
+ declare function apply(ctx: Context, config: PluginConfig): Promise<void>;
407
+ //#endregion
408
+ export { AUTH_PREFIX, AccessController, type AccessControllerOptions, AccessError, type AuthoritySpec, BoundedRateLimiter, CSRF_COOKIE, CSRF_HEADER, Config, DEVICE_COOKIE, type DeviceSnapshot, type DeviceStore, type DeviceSummary, type DisabledTlsConfig, JsonDeviceStore, JsonMobileAccessControlStore, LOCAL_ADMIN_PREFIX, MemoryDeviceStore, type MobileAccessControlState, type MobileAccessControlStore, MobileAccessGateway, MobileAccessGatewayController, type MobileAccessRuntime, type PairingResult, type ParsedCidr, type PluginConfig, type ProvidedTlsConfig, type RenewalResult, RequestTrustPolicy, type ResolvedGatewayConfig, SESSION_COOKIE, type SessionAuthorization, type StoredDevice, type TlsConfig, WS_PATHS, addressAllowed, apply, inject, isLoopbackAddress, name, parseAuthority, parseCidr, parseControlFile, parseDeviceSnapshot, parseGatewayConfig, parseMobileAccessControlState, resolveAuthority };
409
+ //# sourceMappingURL=index.d.mts.map