@saluzi/saluzi-edu 0.2.31 → 0.2.33
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saluzi/saluzi-edu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.33",
|
|
4
4
|
"description": "Saluzi CLI - interactive AI coding assistant in the terminal",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -58,6 +58,8 @@
|
|
|
58
58
|
"check:unused": "knip-bun",
|
|
59
59
|
"health": "bun run scripts/health-check.ts",
|
|
60
60
|
"docs:dev": "npx mintlify dev",
|
|
61
|
+
"build:guide": "vite build packages/guide-web && bun run scripts/build-guide.ts",
|
|
62
|
+
"dev:guide": "vite dev packages/guide-web",
|
|
61
63
|
"typecheck": "tsc --noEmit",
|
|
62
64
|
"precheck": "bun run typecheck && bun run check:fix && bun test --isolate",
|
|
63
65
|
"rcs": "bun run scripts/rcs.ts",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Hono } from 'hono'
|
|
2
|
+
import { getCookie } from 'hono/cookie'
|
|
2
3
|
import { randomUUID } from 'node:crypto'
|
|
3
4
|
import type { Context } from 'hono'
|
|
4
5
|
import type { UpgradeWebSocket } from 'hono/ws'
|
|
@@ -14,6 +15,8 @@ import {
|
|
|
14
15
|
} from '../../auth/middleware'
|
|
15
16
|
import { validateApiKey } from '../../auth/api-key'
|
|
16
17
|
import { resolveToken } from '../../auth/token'
|
|
18
|
+
import { resolveSessionToken } from '../../auth/session'
|
|
19
|
+
import { getDb } from '../../db/sqlite'
|
|
17
20
|
import {
|
|
18
21
|
handleAcpWsOpen,
|
|
19
22
|
handleAcpWsMessage,
|
|
@@ -55,9 +58,47 @@ function toAcpAgentResponse(env: ReturnType<typeof storeGetEnvironment> & {}) {
|
|
|
55
58
|
}
|
|
56
59
|
}
|
|
57
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Resolve a Web UI user's token (SQLite session token from cookie or
|
|
63
|
+
* Bearer) to a userId. Returns null if the token is invalid or absent.
|
|
64
|
+
*
|
|
65
|
+
* Web UI users authenticate via rct_* SQLite session tokens stored in the
|
|
66
|
+
* rcs_access cookie (or Bearer for API calls). ACP read/relay endpoints
|
|
67
|
+
* must accept these in addition to admin API keys, otherwise non-admin
|
|
68
|
+
* users can list ACP sessions (via /web/sessions with sessionAuth) but
|
|
69
|
+
* can't open them (ACP endpoints reject with 401 "unauthorized").
|
|
70
|
+
*/
|
|
71
|
+
function resolveWebUserToken(c: Context): string | null {
|
|
72
|
+
// Cookie (rcs_access) — primary Web UI auth path for WebSocket upgrades.
|
|
73
|
+
// Wrapped because getCookie accesses c.req.raw.headers, which may not
|
|
74
|
+
// exist on mock contexts in unit tests.
|
|
75
|
+
let cookieToken: string | undefined
|
|
76
|
+
try {
|
|
77
|
+
cookieToken = getCookie(c, 'rcs_access')
|
|
78
|
+
} catch {
|
|
79
|
+
cookieToken = undefined
|
|
80
|
+
}
|
|
81
|
+
// Bearer — for REST calls from Web UI (Authorization header)
|
|
82
|
+
const bearerToken = extractBearerToken(c)
|
|
83
|
+
const token = cookieToken || bearerToken
|
|
84
|
+
if (!token) return null
|
|
85
|
+
// Try in-memory token (legacy issueToken flow)
|
|
86
|
+
const username = resolveToken(token)
|
|
87
|
+
if (username) return username
|
|
88
|
+
// Try SQLite session token
|
|
89
|
+
try {
|
|
90
|
+
return resolveSessionToken(token, getDb())
|
|
91
|
+
} catch {
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
58
96
|
function hasAcpReadAuth(c: Context): boolean {
|
|
59
|
-
|
|
60
|
-
|
|
97
|
+
// Admin API key (RCS_API_KEYS)
|
|
98
|
+
const bearer = extractBearerToken(c)
|
|
99
|
+
if (bearer && validateApiKey(bearer)) return true
|
|
100
|
+
// Web UI user (SQLite session token or in-memory token)
|
|
101
|
+
return !!resolveWebUserToken(c)
|
|
61
102
|
}
|
|
62
103
|
|
|
63
104
|
/**
|
|
@@ -68,10 +109,36 @@ function hasAcpReadAuth(c: Context): boolean {
|
|
|
68
109
|
*/
|
|
69
110
|
export function hasAcpRelayAuth(c: Context): boolean {
|
|
70
111
|
const token = extractWebSocketAuthToken(c)
|
|
71
|
-
if (
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
112
|
+
if (token) {
|
|
113
|
+
if (validateApiKey(token)) return true
|
|
114
|
+
// Try in-memory token resolution (existing issueToken flow)
|
|
115
|
+
if (resolveToken(token)) return true
|
|
116
|
+
// Try SQLite session token (rct_* from Bearer or Sec-WebSocket-Protocol)
|
|
117
|
+
try {
|
|
118
|
+
if (resolveSessionToken(token, getDb())) return true
|
|
119
|
+
} catch {
|
|
120
|
+
// DB unavailable — fall through to cookie check
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Cookie-based session token (rcs_access) — browser WebSocket upgrades
|
|
124
|
+
// automatically send cookies, so this works without client-side token
|
|
125
|
+
// injection. Wrapped because getCookie accesses c.req.raw.headers,
|
|
126
|
+
// which may not exist on mock contexts in unit tests.
|
|
127
|
+
let cookieToken: string | undefined
|
|
128
|
+
try {
|
|
129
|
+
cookieToken = getCookie(c, 'rcs_access')
|
|
130
|
+
} catch {
|
|
131
|
+
cookieToken = undefined
|
|
132
|
+
}
|
|
133
|
+
if (cookieToken) {
|
|
134
|
+
if (resolveToken(cookieToken)) return true
|
|
135
|
+
try {
|
|
136
|
+
if (resolveSessionToken(cookieToken, getDb())) return true
|
|
137
|
+
} catch {
|
|
138
|
+
return false
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return false
|
|
75
142
|
}
|
|
76
143
|
|
|
77
144
|
function acpReadUnauthorized(c: Context) {
|