@fonlan/dsh-web-auth 0.1.8 → 0.1.10
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/lib/client/index.d.ts +2 -1
- package/lib/client.js +20 -17
- package/lib/gate.d.ts +7 -2
- package/lib/gate.js +18 -8
- package/lib/handlers.d.ts +13 -1
- package/lib/handlers.js +118 -2
- package/lib/index.d.ts +39 -4
- package/lib/index.js +24 -4
- package/package.json +9 -10
package/lib/client/index.d.ts
CHANGED
|
@@ -4,7 +4,8 @@ import type { Context } from '@deepseek-ai/cordis';
|
|
|
4
4
|
* `connection` and `remote` are required by `settingsScope.bind` (it resolves
|
|
5
5
|
* the settings transport and the forwarded-invalidation subscription through
|
|
6
6
|
* `ctx.get`), so the fiber must wait for them — exactly like
|
|
7
|
-
* the settings shell does for its own sections.
|
|
7
|
+
* the settings shell does for its own sections. dsh >= 0.1.7 dropped
|
|
8
|
+
* `settingsScope` itself, so it is resolved optionally below.
|
|
8
9
|
*/
|
|
9
10
|
export declare const inject: string[];
|
|
10
11
|
/**
|
package/lib/client.js
CHANGED
|
@@ -37,7 +37,7 @@ var dshWebAuthModule = (() => {
|
|
|
37
37
|
});
|
|
38
38
|
var import_react = __require("react");
|
|
39
39
|
var import_jsx_runtime = __require("react/jsx-runtime");
|
|
40
|
-
var inject = ["slots"
|
|
40
|
+
var inject = ["slots"];
|
|
41
41
|
var WEB_AUTH_NS = "web-auth";
|
|
42
42
|
var CSS = `
|
|
43
43
|
.dwa-page { display: flex; flex-direction: column; gap: 16px; max-width: 720px; }
|
|
@@ -285,22 +285,25 @@ var dshWebAuthModule = (() => {
|
|
|
285
285
|
] });
|
|
286
286
|
}
|
|
287
287
|
function apply(ctx) {
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
288
|
+
ctx.inject(["settingsScope"], (raw) => {
|
|
289
|
+
const c = raw;
|
|
290
|
+
if (c.settingsScope === void 0) return;
|
|
291
|
+
injectStyle();
|
|
292
|
+
const scope = c.settingsScope.bind({ namespace: WEB_AUTH_NS });
|
|
293
|
+
c.slots.inject(
|
|
294
|
+
"settings.section",
|
|
295
|
+
() => c.slots.register(
|
|
296
|
+
{
|
|
297
|
+
name: "settings.section",
|
|
298
|
+
id: "web-auth",
|
|
299
|
+
order: 120,
|
|
300
|
+
label: "\u8BBF\u95EE\u8BA4\u8BC1",
|
|
301
|
+
inject: () => ({ scope })
|
|
302
|
+
},
|
|
303
|
+
WebAuthSettingsSection
|
|
304
|
+
)
|
|
305
|
+
);
|
|
306
|
+
});
|
|
304
307
|
}
|
|
305
308
|
return __toCommonJS(index_exports);
|
|
306
309
|
})();
|
package/lib/gate.d.ts
CHANGED
|
@@ -25,8 +25,13 @@ export interface GateHandlers {
|
|
|
25
25
|
* rejection must not prevent the downstream dispatch.
|
|
26
26
|
*/
|
|
27
27
|
passed?(req: IncomingMessage, res: ServerResponse): void | Promise<void>;
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/**
|
|
29
|
+
* Decide whether one WebSocket upgrade may proceed. May be async: the gate
|
|
30
|
+
* awaits the decision before forwarding, letting a handler repair the
|
|
31
|
+
* request (for example by resolving a session cookie onto its headers)
|
|
32
|
+
* instead of only approving or refusing it.
|
|
33
|
+
*/
|
|
34
|
+
allowUpgrade(req: IncomingMessage): boolean | Promise<boolean>;
|
|
30
35
|
}
|
|
31
36
|
/** Standard HTTP rejection for a denied WebSocket upgrade. */
|
|
32
37
|
export declare function rejectUpgrade(socket: Duplex, status?: number, body?: string): void;
|
package/lib/gate.js
CHANGED
|
@@ -38,14 +38,24 @@ export function installGate(server, handlers) {
|
|
|
38
38
|
}
|
|
39
39
|
});
|
|
40
40
|
server.on('upgrade', (req, socket, head) => {
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
41
|
+
void (async () => {
|
|
42
|
+
let allowed;
|
|
43
|
+
try {
|
|
44
|
+
allowed = await handlers.allowUpgrade(req);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
allowed = false;
|
|
48
|
+
}
|
|
49
|
+
if (!allowed) {
|
|
50
|
+
if (!socket.destroyed && socket.writable)
|
|
51
|
+
rejectUpgrade(socket);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
for (const listener of upgradeListeners) {
|
|
55
|
+
;
|
|
56
|
+
listener(req, socket, head);
|
|
57
|
+
}
|
|
58
|
+
})();
|
|
49
59
|
});
|
|
50
60
|
return () => {
|
|
51
61
|
if (!gated.has(server))
|
package/lib/handlers.d.ts
CHANGED
|
@@ -94,9 +94,21 @@ export declare function parseUrlEncoded(body: string): Record<string, string>;
|
|
|
94
94
|
export declare function setSessionCookie(res: ServerResponse, secret: Buffer, now: number, secure: boolean): void;
|
|
95
95
|
export interface Gate {
|
|
96
96
|
allow(req: IncomingMessage, res: ServerResponse): boolean;
|
|
97
|
-
allowUpgrade(req: IncomingMessage): boolean
|
|
97
|
+
allowUpgrade(req: IncomingMessage): boolean | Promise<boolean>;
|
|
98
98
|
passed?(req: IncomingMessage, res: ServerResponse): void | Promise<void>;
|
|
99
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Name of DSH's authority-bound browser-session cookie for one authority.
|
|
102
|
+
* Mirrors the host's own `dsh-auth-<base64url(sha256(authority))>` scheme; if
|
|
103
|
+
* the scheme ever drifts this only misreads "carried" as "missing", which
|
|
104
|
+
* costs one harmless extra redirect, never a denial.
|
|
105
|
+
*
|
|
106
|
+
* Exported for the integration suite, which models a browser jar: the gate's
|
|
107
|
+
* "already carries the cookie" check is name-based, so a test that stores a
|
|
108
|
+
* differently-named cookie would silently exercise the late-mint path instead
|
|
109
|
+
* of the steady state.
|
|
110
|
+
*/
|
|
111
|
+
export declare function dshAuthCookieName(authority: string): string;
|
|
100
112
|
/** Number of `token` query parameters in the request URL. */
|
|
101
113
|
export declare function tokenQueryCount(req: IncomingMessage): number;
|
|
102
114
|
/**
|
package/lib/handlers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
1
2
|
import { COOKIE_NAME, MIN_PASSWORD_LENGTH, SESSION_TTL_SECONDS, hashPassword, needsSessionRefresh, sanitizeNext, signSession, verifyPassword, verifySession } from "./auth-core.js";
|
|
2
3
|
import { renderLoginPage } from "./login-page.js";
|
|
3
4
|
import { ALLOWED_LISTEN_HOSTS, isAllowedListenHost } from "./profile-patch.js";
|
|
@@ -109,6 +110,35 @@ function clearCookie(res, secure) {
|
|
|
109
110
|
}
|
|
110
111
|
/** Minimum interval between server-side DSH cookie mints (debounce). */
|
|
111
112
|
const DSH_MINT_DEBOUNCE_MS = 1000;
|
|
113
|
+
/** Per-peer cooldown between late-mint redirects (loop breaker). */
|
|
114
|
+
const LATE_MINT_COOLDOWN_MS = 5000;
|
|
115
|
+
/**
|
|
116
|
+
* Name of DSH's authority-bound browser-session cookie for one authority.
|
|
117
|
+
* Mirrors the host's own `dsh-auth-<base64url(sha256(authority))>` scheme; if
|
|
118
|
+
* the scheme ever drifts this only misreads "carried" as "missing", which
|
|
119
|
+
* costs one harmless extra redirect, never a denial.
|
|
120
|
+
*
|
|
121
|
+
* Exported for the integration suite, which models a browser jar: the gate's
|
|
122
|
+
* "already carries the cookie" check is name-based, so a test that stores a
|
|
123
|
+
* differently-named cookie would silently exercise the late-mint path instead
|
|
124
|
+
* of the steady state.
|
|
125
|
+
*/
|
|
126
|
+
export function dshAuthCookieName(authority) {
|
|
127
|
+
return 'dsh-auth-' + createHash('sha256').update(authority).digest('base64url');
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Whether the request already carries a DSH browser-session cookie for the
|
|
131
|
+
* loopback authority every authenticated request is rewritten to. Unknown
|
|
132
|
+
* authority (webserver not bound yet) counts as carried: no intervention.
|
|
133
|
+
*/
|
|
134
|
+
function carriesLoopbackDshCookie(req, authority) {
|
|
135
|
+
if (authority === undefined)
|
|
136
|
+
return true;
|
|
137
|
+
const raw = req.headers.cookie;
|
|
138
|
+
if (raw === undefined)
|
|
139
|
+
return false;
|
|
140
|
+
return cookieValue(raw, dshAuthCookieName(authority)) !== undefined;
|
|
141
|
+
}
|
|
112
142
|
/** Number of `token` query parameters in the request URL. */
|
|
113
143
|
export function tokenQueryCount(req) {
|
|
114
144
|
try {
|
|
@@ -181,6 +211,54 @@ export function createGate(env) {
|
|
|
181
211
|
console.error('[dsh-web-auth] DSH cookie mint failed:', error);
|
|
182
212
|
}
|
|
183
213
|
};
|
|
214
|
+
/** Last late-mint redirect per peer address (loop breaker). */
|
|
215
|
+
const lateMintAt = new Map();
|
|
216
|
+
/**
|
|
217
|
+
* One late-mint redirect per peer per cooldown window. A client that keeps
|
|
218
|
+
* refusing the relayed cookie must not be bounced forever; after the first
|
|
219
|
+
* bounce it passes through and DSH's own response (401 + the relayed
|
|
220
|
+
* cookie) is the fallback, exactly the pre-existing behavior.
|
|
221
|
+
*/
|
|
222
|
+
const lateMintDue = (req) => {
|
|
223
|
+
const peer = req.socket.remoteAddress ?? '';
|
|
224
|
+
const t = now();
|
|
225
|
+
const last = lateMintAt.get(peer);
|
|
226
|
+
if (last !== undefined && t - last < LATE_MINT_COOLDOWN_MS)
|
|
227
|
+
return false;
|
|
228
|
+
lateMintAt.set(peer, t);
|
|
229
|
+
return true;
|
|
230
|
+
};
|
|
231
|
+
/**
|
|
232
|
+
* Answer a session-authenticated request that carries no DSH browser-
|
|
233
|
+
* session cookie for the loopback authority: mint the cookie server-side
|
|
234
|
+
* and bounce once through the same URL so the browser stores it. Without
|
|
235
|
+
* this, a browser holding only the plugin session reaches DSH and gets its
|
|
236
|
+
* raw 401 dead end ("reopen the URL printed by dsh web"), recovering only
|
|
237
|
+
* if the user happens to visit again; the redirect makes the first visit
|
|
238
|
+
* succeed. Falls back to /login when the mint is unavailable — that page
|
|
239
|
+
* retries the mint and offers the password form.
|
|
240
|
+
*/
|
|
241
|
+
const respondWithLateMint = async (req, res) => {
|
|
242
|
+
const mint = env.mintDshCookie;
|
|
243
|
+
const cookie = mint === undefined ? undefined : await mint().catch(() => undefined);
|
|
244
|
+
// Share the mint throttle with the opportunistic mint in `passed()`: the
|
|
245
|
+
// retry that follows this bounce would otherwise spend a second loopback
|
|
246
|
+
// exchange to mint the very cookie it just stored.
|
|
247
|
+
if (cookie !== undefined)
|
|
248
|
+
lastMintAt = now();
|
|
249
|
+
if (res.headersSent)
|
|
250
|
+
return;
|
|
251
|
+
if (cookie === undefined) {
|
|
252
|
+
redirect(res, '/login?next=' + encodeURIComponent(sanitizeNext(req.url) ?? '/'));
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
res.writeHead(302, {
|
|
256
|
+
'cache-control': 'no-store',
|
|
257
|
+
location: req.url ?? '/',
|
|
258
|
+
'set-cookie': cookie
|
|
259
|
+
});
|
|
260
|
+
res.end();
|
|
261
|
+
};
|
|
184
262
|
/**
|
|
185
263
|
* Present AUTHENTICATED traffic as loopback to every downstream
|
|
186
264
|
* browser-trust fence, without tracking plugin path prefixes. The /api
|
|
@@ -231,10 +309,22 @@ export function createGate(env) {
|
|
|
231
309
|
// response, so the post-login / request is already authenticated and
|
|
232
310
|
// the browser never needs the token itself.
|
|
233
311
|
if (method === 'GET' && pathname === '/' && tokenQueryCount(req) > 0) {
|
|
234
|
-
if (
|
|
312
|
+
if (isMintReentry(req)) {
|
|
235
313
|
rewriteAsLoopback(req);
|
|
236
314
|
return true;
|
|
237
315
|
}
|
|
316
|
+
const tokenedSession = sessionOf(req);
|
|
317
|
+
if (tokenedSession !== undefined) {
|
|
318
|
+
if (needsSessionRefresh(tokenedSession, now())) {
|
|
319
|
+
setSessionCookie(res, env.state.secret, now(), isSecureRequest(req));
|
|
320
|
+
}
|
|
321
|
+
rewriteAsLoopback(req);
|
|
322
|
+
if (!carriesLoopbackDshCookie(req, env.loopbackAuthority) && lateMintDue(req)) {
|
|
323
|
+
void respondWithLateMint(req, res);
|
|
324
|
+
return false;
|
|
325
|
+
}
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
238
328
|
redirect(res, '/login?next=' + encodeURIComponent(sanitizeNext(pathname) ?? '/'));
|
|
239
329
|
return false;
|
|
240
330
|
}
|
|
@@ -244,6 +334,12 @@ export function createGate(env) {
|
|
|
244
334
|
setSessionCookie(res, env.state.secret, now(), isSecureRequest(req));
|
|
245
335
|
}
|
|
246
336
|
rewriteAsLoopback(req);
|
|
337
|
+
if ((method === 'GET' || method === 'HEAD') &&
|
|
338
|
+
!carriesLoopbackDshCookie(req, env.loopbackAuthority) &&
|
|
339
|
+
lateMintDue(req)) {
|
|
340
|
+
void respondWithLateMint(req, res);
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
247
343
|
return true;
|
|
248
344
|
}
|
|
249
345
|
if (method === 'GET' || method === 'HEAD') {
|
|
@@ -255,10 +351,30 @@ export function createGate(env) {
|
|
|
255
351
|
}
|
|
256
352
|
return false;
|
|
257
353
|
},
|
|
258
|
-
allowUpgrade(req) {
|
|
354
|
+
async allowUpgrade(req) {
|
|
259
355
|
if (sessionOf(req) === undefined)
|
|
260
356
|
return false;
|
|
261
357
|
rewriteAsLoopback(req);
|
|
358
|
+
// A WebSocket upgrade carries cookies only through its request headers,
|
|
359
|
+
// and there is no redirect to repair a missing one — so resolve the DSH
|
|
360
|
+
// browser cookie onto this very request before the host's own gate reads
|
|
361
|
+
// it. Without this, a browser that reached the app through a cached page
|
|
362
|
+
// (no HTTP round-trip, so no late-mint) has every /api/remote.mux upgrade
|
|
363
|
+
// refused: all remote calls fail, surfacing as errors like the welcome
|
|
364
|
+
// acknowledgement failing to save.
|
|
365
|
+
if (!carriesLoopbackDshCookie(req, env.loopbackAuthority)) {
|
|
366
|
+
const mint = env.mintDshCookie;
|
|
367
|
+
const cookie = mint === undefined ? undefined : await mint().catch(() => undefined);
|
|
368
|
+
if (cookie !== undefined) {
|
|
369
|
+
// Same shared throttle as the other mint sites (see respondWithLateMint).
|
|
370
|
+
lastMintAt = now();
|
|
371
|
+
const pair = cookie.split(';')[0]?.trim();
|
|
372
|
+
if (pair !== undefined && pair.length > 0) {
|
|
373
|
+
req.headers.cookie =
|
|
374
|
+
req.headers.cookie === undefined ? pair : req.headers.cookie + '; ' + pair;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
}
|
|
262
378
|
return true;
|
|
263
379
|
},
|
|
264
380
|
async passed(req, res) {
|
package/lib/index.d.ts
CHANGED
|
@@ -16,12 +16,47 @@ export interface WebAuthConfig {
|
|
|
16
16
|
*/
|
|
17
17
|
unlockRemoteSettings: boolean;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
|
|
19
|
+
/**
|
|
20
|
+
* One `volatile()` config field as the loader hands it to `apply()`: a live
|
|
21
|
+
* handle, not the value it currently holds. Spelled structurally rather than as
|
|
22
|
+
* cosmokit's `Volatile<T>` — cosmokit is a transitive dependency here, and
|
|
23
|
+
* importing it would put a second cosmokit/schemastery pair into the emitted
|
|
24
|
+
* declaration.
|
|
25
|
+
*/
|
|
26
|
+
export interface LiveSettingsField<T> {
|
|
27
|
+
get(): T;
|
|
28
|
+
}
|
|
29
|
+
/** Hand-written entry config (profile patch / test fixture): the field is optional. */
|
|
30
|
+
export interface WebAuthConfigInput {
|
|
31
|
+
unlockRemoteSettings?: boolean | null;
|
|
32
|
+
}
|
|
33
|
+
/** The resolved entry config `apply()` receives: the field is a live handle. */
|
|
34
|
+
export interface ResolvedWebAuthConfig {
|
|
35
|
+
unlockRemoteSettings: LiveSettingsField<boolean>;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Composition config schema (cordis Standard-Schema path).
|
|
39
|
+
*
|
|
40
|
+
* `.volatile()` is REQUIRED here, not cosmetic: dsh >= 0.1.7 derives the
|
|
41
|
+
* plugin's settings page from this schema, and `dsh-settings` SKIPS an entry
|
|
42
|
+
* whose schema declares no volatile field (`volatileForm(schema)` is undefined)
|
|
43
|
+
* while refusing every write to a non-volatile path — so without it this
|
|
44
|
+
* plugin's page disappears from Settings entirely, whichever page renders it.
|
|
45
|
+
*
|
|
46
|
+
* The schema is annotated through the package's own default export with locally
|
|
47
|
+
* declared type arguments: schemastery >= 3.18.3 carries a third `Mode` type
|
|
48
|
+
* parameter, so a `volatile()` field's OUTPUT type is `Volatile<T>` and the
|
|
49
|
+
* old `z<WebAuthConfig>` annotation no longer compiles; naming it here also
|
|
50
|
+
* keeps the declaration build portable (TS2742 otherwise).
|
|
51
|
+
*/
|
|
52
|
+
export type WebAuthConfigSchema = z<WebAuthConfigInput, ResolvedWebAuthConfig>;
|
|
53
|
+
/** Resolved schema for the loader and the settings plane. */
|
|
54
|
+
export declare const Config: WebAuthConfigSchema;
|
|
21
55
|
/**
|
|
22
56
|
* Mount the auth gate and its routes.
|
|
23
57
|
* @param ctx - plugin context carrying the webServer service (listening).
|
|
24
58
|
* @param config - validated composition config (Config schema defaults when
|
|
25
|
-
* the patch row spells none)
|
|
59
|
+
* the patch row spells none); `unlockRemoteSettings` may arrive as a live
|
|
60
|
+
* volatile handle and is unwrapped below.
|
|
26
61
|
*/
|
|
27
|
-
export declare function apply(ctx: PluginContext, config: WebAuthConfig): void;
|
|
62
|
+
export declare function apply(ctx: PluginContext, config: WebAuthConfig | ResolvedWebAuthConfig): void;
|
package/lib/index.js
CHANGED
|
@@ -31,17 +31,37 @@ import z from '@deepseek-ai/schemastery';
|
|
|
31
31
|
export const name = 'web-auth';
|
|
32
32
|
/** Services required before the gate can be installed. */
|
|
33
33
|
export const inject = ['webServer'];
|
|
34
|
-
/**
|
|
34
|
+
/** Resolved schema for the loader and the settings plane. */
|
|
35
35
|
export const Config = z.object({
|
|
36
|
-
unlockRemoteSettings: z.boolean().default(false)
|
|
36
|
+
unlockRemoteSettings: z.boolean().default(false).volatile()
|
|
37
37
|
});
|
|
38
|
+
/**
|
|
39
|
+
* Read a config field that may be a plain value or a live (`.volatile()`) node.
|
|
40
|
+
*
|
|
41
|
+
* The loader resolves a volatile field to a cosmokit cell (`{ get() }`), which
|
|
42
|
+
* is an object and therefore always truthy — reading it directly would turn the
|
|
43
|
+
* opt-in flag on everywhere. Hand-written configs (patch fixtures, tests) pass
|
|
44
|
+
* plain values, so both shapes are accepted here.
|
|
45
|
+
*/
|
|
46
|
+
function liveValue(node, fallback) {
|
|
47
|
+
if (node === undefined || node === null)
|
|
48
|
+
return fallback;
|
|
49
|
+
const getter = node.get;
|
|
50
|
+
if (typeof getter === 'function') {
|
|
51
|
+
const value = getter.call(node);
|
|
52
|
+
return value === undefined ? fallback : value;
|
|
53
|
+
}
|
|
54
|
+
return node;
|
|
55
|
+
}
|
|
38
56
|
/**
|
|
39
57
|
* Mount the auth gate and its routes.
|
|
40
58
|
* @param ctx - plugin context carrying the webServer service (listening).
|
|
41
59
|
* @param config - validated composition config (Config schema defaults when
|
|
42
|
-
* the patch row spells none)
|
|
60
|
+
* the patch row spells none); `unlockRemoteSettings` may arrive as a live
|
|
61
|
+
* volatile handle and is unwrapped below.
|
|
43
62
|
*/
|
|
44
63
|
export function apply(ctx, config) {
|
|
64
|
+
const unlockRemoteSettings = liveValue(config.unlockRemoteSettings, false);
|
|
45
65
|
const dshHome = resolveDshHome();
|
|
46
66
|
const state = {
|
|
47
67
|
secret: readSecret(dshHome),
|
|
@@ -116,7 +136,7 @@ export function apply(ctx, config) {
|
|
|
116
136
|
// ── remote settings unlock (opt-in): inject ownsHost into the boot page ───
|
|
117
137
|
// A config-only patch change restarts this fiber (cordis update →
|
|
118
138
|
// dispose + re-apply), so the tap flips live with the composition.
|
|
119
|
-
if (
|
|
139
|
+
if (unlockRemoteSettings) {
|
|
120
140
|
ctx.effect(() => ctx.webServer.tapIndex(injectOwnsHost), 'web-auth: remote settings unlock tap');
|
|
121
141
|
}
|
|
122
142
|
// ── routes (exact table; /api/web-auth/password wins over the /api prefix) ─
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonlan/dsh-web-auth",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.10",
|
|
4
4
|
"description": "DSH web plugin: password-gate authentication (login page + HttpOnly signed cookie) for exposing dsh web behind a reverse proxy",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -59,21 +59,20 @@
|
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
61
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
62
|
-
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
|
|
63
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
62
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.2 || ^0.2.0-rc.1 || ^0.2.1-alpha.1",
|
|
63
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
64
64
|
"react": "^18.2.0",
|
|
65
65
|
"react-dom": "^18.2.0"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@deepseek-ai/
|
|
69
|
-
"@deepseek-ai/schemastery": "^3.18.2"
|
|
68
|
+
"@deepseek-ai/schemastery": "3.18.5-alpha.1"
|
|
70
69
|
},
|
|
71
70
|
"devDependencies": {
|
|
72
|
-
"@deepseek-ai/cordis": "
|
|
73
|
-
"@deepseek-ai/dsh-client-ui-slots": "
|
|
74
|
-
"@deepseek-ai/dsh-host-webserver": "
|
|
75
|
-
"@deepseek-ai/dsh-settings": "
|
|
76
|
-
"@deepseek-ai/schemastery": "
|
|
71
|
+
"@deepseek-ai/cordis": "4.0.5-alpha.1",
|
|
72
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.1-alpha.1",
|
|
73
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.1-alpha.1",
|
|
74
|
+
"@deepseek-ai/dsh-settings": "0.2.1-alpha.1",
|
|
75
|
+
"@deepseek-ai/schemastery": "3.18.5-alpha.1",
|
|
77
76
|
"@types/node": "^22.0.0",
|
|
78
77
|
"@types/react": "^18.3.0",
|
|
79
78
|
"esbuild": "^0.24.0",
|