@camstack/server 1.0.0 → 1.0.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.
Files changed (234) hide show
  1. package/{src/agent-status-page.ts → dist/agent-status-page.js} +30 -45
  2. package/dist/api/addon-upload.js +441 -0
  3. package/dist/api/addons-custom.router.js +91 -0
  4. package/dist/api/auth-whoami.js +55 -0
  5. package/dist/api/bridge-addons.router.js +109 -0
  6. package/dist/api/capabilities.router.js +229 -0
  7. package/dist/api/core/addon-settings.router.js +117 -0
  8. package/dist/api/core/agents.router.js +73 -0
  9. package/dist/api/core/auth.router.js +286 -0
  10. package/dist/api/core/bulk-update-coordinator.js +229 -0
  11. package/dist/api/core/cap-providers.js +1124 -0
  12. package/dist/api/core/capabilities.router.js +138 -0
  13. package/dist/api/core/collection-preference.js +17 -0
  14. package/dist/api/core/event-bus-proxy.router.js +45 -0
  15. package/dist/api/core/hwaccel.router.js +91 -0
  16. package/dist/api/core/live-events.router.js +61 -0
  17. package/dist/api/core/logs.router.js +172 -0
  18. package/dist/api/core/notifications.router.js +67 -0
  19. package/dist/api/core/repl.router.js +35 -0
  20. package/dist/api/core/settings-backend.router.js +121 -0
  21. package/dist/api/core/stream-probe.router.js +58 -0
  22. package/dist/api/core/system-events.router.js +100 -0
  23. package/dist/api/health/health.routes.js +68 -0
  24. package/{src/api/oauth2/consent-page.ts → dist/api/oauth2/consent-page.js} +11 -20
  25. package/dist/api/oauth2/oauth2-routes.js +219 -0
  26. package/dist/api/trpc/cap-mount-helpers.js +194 -0
  27. package/dist/api/trpc/cap-route-error-formatter.js +133 -0
  28. package/dist/api/trpc/client-ip.js +147 -0
  29. package/dist/api/trpc/core-cap-bridge.js +115 -0
  30. package/dist/api/trpc/generated-cap-mounts.js +388 -0
  31. package/dist/api/trpc/generated-cap-routers.js +7635 -0
  32. package/dist/api/trpc/scope-access.js +93 -0
  33. package/dist/api/trpc/trpc.context.js +184 -0
  34. package/dist/api/trpc/trpc.middleware.js +139 -0
  35. package/dist/api/trpc/trpc.router.js +188 -0
  36. package/dist/auth/session-cookie.js +47 -0
  37. package/dist/boot/boot-config.js +241 -0
  38. package/dist/boot/integration-id-backfill.js +76 -0
  39. package/dist/boot/post-boot.service.js +85 -0
  40. package/dist/core/addon/addon-call-gateway.js +99 -0
  41. package/dist/core/addon/addon-package.service.js +1560 -0
  42. package/dist/core/addon/addon-registry.service.js +2739 -0
  43. package/{src/core/addon/addon-row-manifest.ts → dist/core/addon/addon-row-manifest.js} +5 -5
  44. package/dist/core/addon/addon-search.service.js +62 -0
  45. package/dist/core/addon/addon-settings-provider.js +102 -0
  46. package/dist/core/addon/addon.tokens.js +5 -0
  47. package/dist/core/addon-bridge/addon-bridge.service.js +145 -0
  48. package/dist/core/addon-pages/addon-pages.service.js +107 -0
  49. package/dist/core/addon-widgets/addon-widgets.service.js +120 -0
  50. package/dist/core/agent/agent-registry.service.js +477 -0
  51. package/dist/core/auth/auth.service.js +10 -0
  52. package/dist/core/capability/capability.service.js +58 -0
  53. package/dist/core/config/config.schema.js +7 -0
  54. package/dist/core/config/config.service.js +10 -0
  55. package/dist/core/events/event-bus.service.js +83 -0
  56. package/dist/core/feature/feature.service.js +10 -0
  57. package/dist/core/lifecycle/lifecycle-state-machine.js +6 -0
  58. package/dist/core/logging/log-ring-buffer.js +6 -0
  59. package/dist/core/logging/logging.service.js +130 -0
  60. package/dist/core/logging/scoped-logger.js +6 -0
  61. package/dist/core/moleculer/cap-call-fn.js +50 -0
  62. package/dist/core/moleculer/cap-route-authority.js +122 -0
  63. package/dist/core/moleculer/moleculer.service.js +898 -0
  64. package/dist/core/network/network-quality.service.js +7 -0
  65. package/dist/core/notification/notification-wrapper.service.js +33 -0
  66. package/dist/core/notification/toast-wrapper.service.js +25 -0
  67. package/dist/core/provider/provider.tokens.js +4 -0
  68. package/dist/core/repl/repl-engine.service.js +140 -0
  69. package/dist/core/storage/fs-storage-backend.js +6 -0
  70. package/dist/core/storage/storage-location-manager.js +6 -0
  71. package/dist/core/storage/storage.service.js +7 -0
  72. package/dist/core/streaming/stream-probe.service.js +209 -0
  73. package/dist/core/topology/topology-emitter.service.js +106 -0
  74. package/dist/launcher.js +325 -0
  75. package/dist/main.js +1098 -0
  76. package/dist/manual-boot.js +227 -0
  77. package/package.json +5 -1
  78. package/src/__tests__/addon-install-e2e.test.ts +0 -74
  79. package/src/__tests__/addon-pages-e2e.test.ts +0 -200
  80. package/src/__tests__/addon-route-session.test.ts +0 -17
  81. package/src/__tests__/addon-settings-router.spec.ts +0 -67
  82. package/src/__tests__/addon-upload.spec.ts +0 -475
  83. package/src/__tests__/agent-registry.spec.ts +0 -179
  84. package/src/__tests__/agent-status-page.spec.ts +0 -82
  85. package/src/__tests__/auth-session-cookie.test.ts +0 -48
  86. package/src/__tests__/bulk-update-coordinator.spec.ts +0 -303
  87. package/src/__tests__/cap-ownership-authority.spec.ts +0 -431
  88. package/src/__tests__/cap-providers/cap-providers-location-import.spec.ts +0 -206
  89. package/src/__tests__/cap-providers/cap-usage-graph.spec.ts +0 -37
  90. package/src/__tests__/cap-providers/compute-topology-categories.spec.ts +0 -110
  91. package/src/__tests__/cap-providers/integrations-delete-cascade.spec.ts +0 -292
  92. package/src/__tests__/cap-providers-bulk-update.spec.ts +0 -408
  93. package/src/__tests__/cap-route-adapter.spec.ts +0 -302
  94. package/src/__tests__/cap-routers/_meta.spec.ts +0 -199
  95. package/src/__tests__/cap-routers/addon-settings.router.spec.ts +0 -115
  96. package/src/__tests__/cap-routers/broker-routing.router.spec.ts +0 -177
  97. package/src/__tests__/cap-routers/cap-route-error-formatter.spec.ts +0 -125
  98. package/src/__tests__/cap-routers/capabilities-node.spec.ts +0 -68
  99. package/src/__tests__/cap-routers/device-link-overlay.spec.ts +0 -137
  100. package/src/__tests__/cap-routers/device-manager-aggregate.router.spec.ts +0 -194
  101. package/src/__tests__/cap-routers/harness.ts +0 -163
  102. package/src/__tests__/cap-routers/metrics-provider.router.spec.ts +0 -133
  103. package/src/__tests__/cap-routers/null-provider-guard.spec.ts +0 -64
  104. package/src/__tests__/cap-routers/pipeline-executor.router.spec.ts +0 -159
  105. package/src/__tests__/cap-routers/settings-store.router.spec.ts +0 -291
  106. package/src/__tests__/capability-e2e.test.ts +0 -384
  107. package/src/__tests__/cli-e2e.test.ts +0 -150
  108. package/src/__tests__/core-cap-bridge.spec.ts +0 -91
  109. package/src/__tests__/dev-bootstrap-shm-ring.spec.ts +0 -40
  110. package/src/__tests__/device-settings-contribution-dispatch.spec.ts +0 -280
  111. package/src/__tests__/embedded-deps-e2e.test.ts +0 -125
  112. package/src/__tests__/event-bus-proxy-router.spec.ts +0 -75
  113. package/src/__tests__/fixtures/mock-analysis-addon-a.ts +0 -37
  114. package/src/__tests__/fixtures/mock-analysis-addon-b.ts +0 -37
  115. package/src/__tests__/fixtures/mock-log-addon.ts +0 -37
  116. package/src/__tests__/fixtures/mock-storage-addon.ts +0 -40
  117. package/src/__tests__/framework-allowlist.spec.ts +0 -96
  118. package/src/__tests__/framework-installer-defer-restart.spec.ts +0 -165
  119. package/src/__tests__/https-e2e.test.ts +0 -124
  120. package/src/__tests__/lifecycle-e2e.test.ts +0 -189
  121. package/src/__tests__/live-events-subscription.spec.ts +0 -149
  122. package/src/__tests__/moleculer/uds-readiness.spec.ts +0 -150
  123. package/src/__tests__/moleculer/uds-topology.spec.ts +0 -418
  124. package/src/__tests__/moleculer/uds-unowned-call.spec.ts +0 -383
  125. package/src/__tests__/moleculer-register-node-idempotency.spec.ts +0 -273
  126. package/src/__tests__/native-cap-route.spec.ts +0 -427
  127. package/src/__tests__/oauth2-account-linking.spec.ts +0 -867
  128. package/src/__tests__/post-boot-restart.spec.ts +0 -161
  129. package/src/__tests__/singleton-contention.test.ts +0 -499
  130. package/src/__tests__/streaming-diagnostic.test.ts +0 -615
  131. package/src/__tests__/streaming-scale.test.ts +0 -314
  132. package/src/__tests__/uds-addon-call-wiring.spec.ts +0 -242
  133. package/src/__tests__/uds-log-ingest.spec.ts +0 -183
  134. package/src/api/__tests__/addons-custom.spec.ts +0 -148
  135. package/src/api/__tests__/capabilities.router.test.ts +0 -56
  136. package/src/api/addon-upload.ts +0 -529
  137. package/src/api/addons-custom.router.ts +0 -101
  138. package/src/api/auth-whoami.ts +0 -101
  139. package/src/api/bridge-addons.router.ts +0 -122
  140. package/src/api/capabilities.router.ts +0 -265
  141. package/src/api/core/__tests__/auth-router-totp.spec.ts +0 -297
  142. package/src/api/core/__tests__/integration-markers.spec.ts +0 -10
  143. package/src/api/core/addon-settings.router.ts +0 -127
  144. package/src/api/core/agents.router.ts +0 -86
  145. package/src/api/core/auth.router.ts +0 -322
  146. package/src/api/core/bulk-update-coordinator.ts +0 -305
  147. package/src/api/core/cap-providers.ts +0 -1339
  148. package/src/api/core/capabilities.router.ts +0 -149
  149. package/src/api/core/collection-preference.ts +0 -40
  150. package/src/api/core/event-bus-proxy.router.ts +0 -45
  151. package/src/api/core/hwaccel.router.ts +0 -108
  152. package/src/api/core/live-events.router.ts +0 -67
  153. package/src/api/core/logs.router.ts +0 -195
  154. package/src/api/core/notifications.router.ts +0 -66
  155. package/src/api/core/repl.router.ts +0 -39
  156. package/src/api/core/settings-backend.router.ts +0 -140
  157. package/src/api/core/stream-probe.router.ts +0 -57
  158. package/src/api/core/system-events.router.ts +0 -125
  159. package/src/api/health/health.routes.ts +0 -117
  160. package/src/api/oauth2/__tests__/oauth2-routes.spec.ts +0 -62
  161. package/src/api/oauth2/oauth2-routes.ts +0 -281
  162. package/src/api/trpc/__tests__/client-ip.spec.ts +0 -146
  163. package/src/api/trpc/__tests__/scope-access-device.spec.ts +0 -268
  164. package/src/api/trpc/__tests__/scope-access.spec.ts +0 -102
  165. package/src/api/trpc/__tests__/webrtc-session-ua-enrich.spec.ts +0 -136
  166. package/src/api/trpc/cap-mount-helpers.ts +0 -245
  167. package/src/api/trpc/cap-route-error-formatter.ts +0 -171
  168. package/src/api/trpc/client-ip.ts +0 -147
  169. package/src/api/trpc/core-cap-bridge.ts +0 -154
  170. package/src/api/trpc/generated-cap-mounts.ts +0 -1240
  171. package/src/api/trpc/generated-cap-routers.ts +0 -11523
  172. package/src/api/trpc/scope-access.ts +0 -110
  173. package/src/api/trpc/trpc.context.ts +0 -258
  174. package/src/api/trpc/trpc.middleware.ts +0 -146
  175. package/src/api/trpc/trpc.router.ts +0 -389
  176. package/src/auth/session-cookie.ts +0 -54
  177. package/src/boot/__tests__/integration-id-backfill.spec.ts +0 -131
  178. package/src/boot/boot-config.ts +0 -259
  179. package/src/boot/integration-id-backfill.ts +0 -109
  180. package/src/boot/post-boot.service.ts +0 -105
  181. package/src/core/addon/__tests__/addon-registry-capability.test.ts +0 -62
  182. package/src/core/addon/__tests__/addon-row-manifest.spec.ts +0 -62
  183. package/src/core/addon/addon-call-gateway.ts +0 -171
  184. package/src/core/addon/addon-package.service.ts +0 -1787
  185. package/src/core/addon/addon-registry.service.ts +0 -3130
  186. package/src/core/addon/addon-search.service.ts +0 -91
  187. package/src/core/addon/addon-settings-provider.ts +0 -220
  188. package/src/core/addon/addon.tokens.ts +0 -2
  189. package/src/core/addon-bridge/addon-bridge.service.ts +0 -130
  190. package/src/core/addon-pages/addon-pages.service.spec.ts +0 -117
  191. package/src/core/addon-pages/addon-pages.service.ts +0 -82
  192. package/src/core/addon-widgets/addon-widgets.service.ts +0 -95
  193. package/src/core/agent/agent-registry.service.ts +0 -529
  194. package/src/core/auth/auth.service.spec.ts +0 -86
  195. package/src/core/auth/auth.service.ts +0 -8
  196. package/src/core/capability/capability.service.ts +0 -66
  197. package/src/core/config/config.schema.ts +0 -3
  198. package/src/core/config/config.service.spec.ts +0 -175
  199. package/src/core/config/config.service.ts +0 -7
  200. package/src/core/events/event-bus.service.spec.ts +0 -235
  201. package/src/core/events/event-bus.service.ts +0 -89
  202. package/src/core/feature/feature.service.spec.ts +0 -99
  203. package/src/core/feature/feature.service.ts +0 -8
  204. package/src/core/lifecycle/lifecycle-state-machine.spec.ts +0 -166
  205. package/src/core/lifecycle/lifecycle-state-machine.ts +0 -3
  206. package/src/core/logging/log-ring-buffer.ts +0 -3
  207. package/src/core/logging/logging.service.spec.ts +0 -287
  208. package/src/core/logging/logging.service.ts +0 -143
  209. package/src/core/logging/scoped-logger.ts +0 -3
  210. package/src/core/moleculer/cap-call-fn.spec.ts +0 -173
  211. package/src/core/moleculer/cap-call-fn.ts +0 -107
  212. package/src/core/moleculer/cap-route-authority.ts +0 -194
  213. package/src/core/moleculer/moleculer.service.ts +0 -1072
  214. package/src/core/network/network-quality.service.spec.ts +0 -53
  215. package/src/core/network/network-quality.service.ts +0 -5
  216. package/src/core/notification/notification-wrapper.service.ts +0 -34
  217. package/src/core/notification/toast-wrapper.service.ts +0 -27
  218. package/src/core/provider/provider.tokens.ts +0 -1
  219. package/src/core/repl/repl-engine.service.spec.ts +0 -444
  220. package/src/core/repl/repl-engine.service.ts +0 -155
  221. package/src/core/storage/fs-storage-backend.spec.ts +0 -70
  222. package/src/core/storage/fs-storage-backend.ts +0 -3
  223. package/src/core/storage/storage-location-manager.spec.ts +0 -130
  224. package/src/core/storage/storage-location-manager.ts +0 -3
  225. package/src/core/storage/storage.service.spec.ts +0 -73
  226. package/src/core/storage/storage.service.ts +0 -3
  227. package/src/core/streaming/stream-probe.service.ts +0 -221
  228. package/src/core/topology/topology-emitter.service.ts +0 -105
  229. package/src/launcher.ts +0 -314
  230. package/src/main.ts +0 -1245
  231. package/src/manual-boot.ts +0 -301
  232. package/tsconfig.build.json +0 -8
  233. package/tsconfig.json +0 -33
  234. package/vitest.config.ts +0 -26
@@ -1,297 +0,0 @@
1
- /* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-argument, @typescript-eslint/no-unsafe-return -- mock factories cross typed boundaries */
2
- import { describe, it, expect, beforeEach, vi } from 'vitest'
3
- import { createAuthRouter } from '../auth.router.js'
4
- import { AuthService } from '../../../core/auth/auth.service.js'
5
-
6
- /**
7
- * Two-phase TOTP login flow tests for `auth.router`. Focus on the
8
- * cap-router contracts the SDK + admin-ui depend on:
9
- *
10
- * • `login` returns `requiresTotp: true` + a short-lived challenge
11
- * token when the user has TOTP enrolled.
12
- * • `login` returns a regular session token when the user has NO
13
- * TOTP (backwards compat).
14
- * • `loginVerifyTotp` validates the challenge token + code and
15
- * mints the real session JWT.
16
- * • Failure paths: wrong code, expired/forged challenge, missing
17
- * user, missing user-management cap.
18
- */
19
-
20
- interface MockUser {
21
- id: string
22
- username: string
23
- isAdmin: boolean
24
- passwordHash: string
25
- allowedProviders: string | string[]
26
- allowedDevices: Record<string, unknown>
27
- scopes: unknown[]
28
- }
29
-
30
- interface MockUserManagement {
31
- validateCredentials: ReturnType<typeof vi.fn>
32
- getTotpStatus: ReturnType<typeof vi.fn>
33
- verifyTotp: ReturnType<typeof vi.fn>
34
- listUsers: ReturnType<typeof vi.fn>
35
- }
36
-
37
- function makeUser(overrides: Partial<MockUser> = {}): MockUser {
38
- return {
39
- id: 'u-1',
40
- username: 'alice',
41
- isAdmin: false,
42
- passwordHash: 'hashed',
43
- allowedProviders: '*',
44
- allowedDevices: {},
45
- scopes: [],
46
- ...overrides,
47
- }
48
- }
49
-
50
- function makeUserMgmt(overrides: Partial<MockUserManagement> = {}): MockUserManagement {
51
- return {
52
- validateCredentials: vi.fn(),
53
- getTotpStatus: vi.fn(async () => ({ enabled: false, confirmedAt: null })),
54
- verifyTotp: vi.fn(async () => ({ valid: true })),
55
- listUsers: vi.fn(async () => [makeUser()]),
56
- ...overrides,
57
- }
58
- }
59
-
60
- function makeConfig(overrides: Record<string, unknown> = {}): {
61
- get<T>(p: string): T
62
- update(s: string, d: Record<string, unknown>): void
63
- } {
64
- const store: Record<string, unknown> = { 'auth.jwtSecret': 'unit-test-secret', ...overrides }
65
- return {
66
- get<T>(path: string): T {
67
- return store[path] as T
68
- },
69
- update(_section: string, _data: Record<string, unknown>): void {
70
- /* no-op */
71
- },
72
- }
73
- }
74
-
75
- function makeRegistry(userMgmt: MockUserManagement): {
76
- getSingleton: (name: string) => unknown | null
77
- } {
78
- return {
79
- getSingleton: (name: string) => (name === 'user-management' ? userMgmt : null),
80
- }
81
- }
82
-
83
- /** Invoke a tRPC procedure directly via the router's internal API. */
84
- async function callProc(
85
- router: ReturnType<typeof createAuthRouter>,
86
- name: 'login' | 'loginVerifyTotp',
87
- input: unknown,
88
- ): Promise<unknown> {
89
- const caller = router.createCaller({} as never)
90
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
91
- const fn = (caller as any)[name]
92
- return fn(input)
93
- }
94
-
95
- describe('auth.router — TOTP gate', () => {
96
- let auth: AuthService
97
- let userMgmt: MockUserManagement
98
- let registry: ReturnType<typeof makeRegistry>
99
-
100
- beforeEach(() => {
101
- auth = new AuthService(makeConfig() as never)
102
- userMgmt = makeUserMgmt()
103
- registry = makeRegistry(userMgmt)
104
- })
105
-
106
- describe('login (phase 1)', () => {
107
- it('returns a regular session token + requiresTotp:false when user has NO TOTP', async () => {
108
- const u = makeUser()
109
- userMgmt.validateCredentials.mockResolvedValueOnce(u)
110
- userMgmt.getTotpStatus.mockResolvedValueOnce({ enabled: false, confirmedAt: null })
111
-
112
- const router = createAuthRouter(auth, registry as never)
113
- const result = (await callProc(router, 'login', { username: 'alice', password: 'pw' })) as {
114
- token: string
115
- user: { id: string; username: string; isAdmin: boolean }
116
- requiresTotp?: boolean
117
- }
118
-
119
- expect(result.requiresTotp).toBe(false)
120
- expect(result.user).toEqual({ id: 'u-1', username: 'alice', isAdmin: false })
121
- // The token verifies as a regular session token (NOT a challenge).
122
- const verified = auth.verifyToken(result.token)
123
- expect(verified.userId).toBe('u-1')
124
- // verifyTotpChallengeToken should NOT recognize a regular session token.
125
- expect(auth.verifySsoBridgeToken(result.token)).toBeNull()
126
- })
127
-
128
- it('returns a challenge token + requiresTotp:true when user HAS TOTP', async () => {
129
- const u = makeUser()
130
- userMgmt.validateCredentials.mockResolvedValueOnce(u)
131
- userMgmt.getTotpStatus.mockResolvedValueOnce({ enabled: true, confirmedAt: Date.now() })
132
-
133
- const router = createAuthRouter(auth, registry as never)
134
- const result = (await callProc(router, 'login', { username: 'alice', password: 'pw' })) as {
135
- token: string
136
- user: { id: string; username: string; isAdmin: boolean }
137
- requiresTotp?: boolean
138
- }
139
-
140
- expect(result.requiresTotp).toBe(true)
141
- // The challenge token MUST NOT verify as a session JWT — it has
142
- // `kind: 'totp-challenge'` and the standard verifyToken expects
143
- // the session shape (it'd throw or return garbage).
144
- const challengeClaims = auth.verifyTotpChallengeToken(result.token)
145
- expect(challengeClaims).not.toBeNull()
146
- expect(challengeClaims!.userId).toBe('u-1')
147
- expect(challengeClaims!.isAdmin).toBe(false)
148
- })
149
-
150
- it('rejects invalid credentials', async () => {
151
- userMgmt.validateCredentials.mockResolvedValueOnce(null)
152
- const router = createAuthRouter(auth, registry as never)
153
- await expect(
154
- callProc(router, 'login', { username: 'alice', password: 'wrong' }),
155
- ).rejects.toThrow('Invalid credentials')
156
- })
157
-
158
- it('throws when user-management cap is unregistered (boot failure)', async () => {
159
- const emptyRegistry = { getSingleton: () => null } as never
160
- const router = createAuthRouter(auth, emptyRegistry)
161
- await expect(
162
- callProc(router, 'login', { username: 'alice', password: 'pw' }),
163
- ).rejects.toThrow(/user-management.*capability not registered/)
164
- })
165
-
166
- it('falls through to session-token branch when getTotpStatus method is absent (older user-mgmt builds)', async () => {
167
- const u = makeUser()
168
- const legacyMgmt = {
169
- validateCredentials: vi.fn().mockResolvedValueOnce(u),
170
- // getTotpStatus deliberately undefined
171
- verifyTotp: vi.fn(),
172
- listUsers: vi.fn(),
173
- }
174
- const router = createAuthRouter(auth, makeRegistry(legacyMgmt as never) as never)
175
- const result = (await callProc(router, 'login', { username: 'alice', password: 'pw' })) as {
176
- requiresTotp?: boolean
177
- }
178
- expect(result.requiresTotp).toBe(false)
179
- })
180
- })
181
-
182
- describe('loginVerifyTotp (phase 2)', () => {
183
- it('mints the real session JWT when challenge + code are both valid', async () => {
184
- const u = makeUser({ scopes: [{ type: 'category', target: 'storage', access: ['view'] }] })
185
- userMgmt.verifyTotp.mockResolvedValueOnce({ valid: true })
186
- userMgmt.listUsers.mockResolvedValueOnce([u])
187
-
188
- const challengeToken = auth.signTotpChallengeToken({
189
- userId: u.id,
190
- username: u.username,
191
- isAdmin: u.isAdmin,
192
- })
193
-
194
- const router = createAuthRouter(auth, registry as never)
195
- const result = (await callProc(router, 'loginVerifyTotp', {
196
- challengeToken,
197
- code: '123456',
198
- })) as {
199
- token: string
200
- user: { id: string; username: string; isAdmin: boolean }
201
- requiresTotp?: boolean
202
- }
203
-
204
- expect(result.requiresTotp).toBe(false)
205
- expect(result.user.id).toBe('u-1')
206
- // Session JWT verifies + carries the user's scopes snapshot.
207
- const decoded = auth.verifyToken(result.token)
208
- expect(decoded.userId).toBe('u-1')
209
- })
210
-
211
- it('rejects an invalid/expired challenge token', async () => {
212
- const router = createAuthRouter(auth, registry as never)
213
- await expect(
214
- callProc(router, 'loginVerifyTotp', { challengeToken: 'not.a.real.jwt', code: '123456' }),
215
- ).rejects.toThrow(/Invalid or expired TOTP challenge/)
216
- })
217
-
218
- it('rejects a forged challenge token (signed with wrong secret)', async () => {
219
- const other = new AuthService(makeConfig({ 'auth.jwtSecret': 'attacker-secret' }) as never)
220
- const forged = other.signTotpChallengeToken({
221
- userId: 'u-1',
222
- username: 'alice',
223
- isAdmin: true,
224
- })
225
- const router = createAuthRouter(auth, registry as never)
226
- await expect(
227
- callProc(router, 'loginVerifyTotp', { challengeToken: forged, code: '000000' }),
228
- ).rejects.toThrow(/Invalid or expired TOTP challenge/)
229
- })
230
-
231
- it('rejects a session token reused as a challenge token (wrong kind)', async () => {
232
- const sessionTok = auth.signToken({
233
- userId: 'u-1',
234
- username: 'alice',
235
- isAdmin: true,
236
- allowedProviders: '*',
237
- allowedDevices: {},
238
- })
239
- const router = createAuthRouter(auth, registry as never)
240
- await expect(
241
- callProc(router, 'loginVerifyTotp', { challengeToken: sessionTok, code: '000000' }),
242
- ).rejects.toThrow(/Invalid or expired TOTP challenge/)
243
- })
244
-
245
- it('rejects a wrong 6-digit code', async () => {
246
- userMgmt.verifyTotp.mockResolvedValueOnce({ valid: false })
247
- const challengeToken = auth.signTotpChallengeToken({
248
- userId: 'u-1',
249
- username: 'alice',
250
- isAdmin: false,
251
- })
252
- const router = createAuthRouter(auth, registry as never)
253
- await expect(
254
- callProc(router, 'loginVerifyTotp', { challengeToken, code: '000000' }),
255
- ).rejects.toThrow(/Invalid TOTP code/)
256
- })
257
-
258
- it('rejects when the user vanishes between legs (deleted concurrently)', async () => {
259
- userMgmt.verifyTotp.mockResolvedValueOnce({ valid: true })
260
- // The fresh listUsers() call returns nothing — user was deleted.
261
- userMgmt.listUsers.mockResolvedValueOnce([])
262
- const challengeToken = auth.signTotpChallengeToken({
263
- userId: 'ghost',
264
- username: 'alice',
265
- isAdmin: false,
266
- })
267
- const router = createAuthRouter(auth, registry as never)
268
- await expect(
269
- callProc(router, 'loginVerifyTotp', { challengeToken, code: '123456' }),
270
- ).rejects.toThrow(/User no longer exists/)
271
- })
272
-
273
- it('uses the FRESHLY fetched user record (scope changes pick up immediately)', async () => {
274
- // Issue challenge for u-1 with empty scopes (the in-token snapshot).
275
- const challengeToken = auth.signTotpChallengeToken({
276
- userId: 'u-1',
277
- username: 'alice',
278
- isAdmin: false,
279
- })
280
- // Between the two legs, an admin granted u-1 a new scope.
281
- userMgmt.verifyTotp.mockResolvedValueOnce({ valid: true })
282
- userMgmt.listUsers.mockResolvedValueOnce([
283
- makeUser({ scopes: [{ type: 'category', target: 'addon', access: ['view'] }] }),
284
- ])
285
- const router = createAuthRouter(auth, registry as never)
286
- const result = (await callProc(router, 'loginVerifyTotp', {
287
- challengeToken,
288
- code: '123456',
289
- })) as { token: string }
290
- const decoded = auth.verifyToken(result.token)
291
- // The fresh scope is in the minted session JWT, not the snapshot
292
- // from the (potentially stale) password leg.
293
- const scopes = (decoded as { scopes?: unknown[] }).scopes
294
- expect(scopes).toHaveLength(1)
295
- })
296
- })
297
- })
@@ -1,10 +0,0 @@
1
- import { describe, it, expect } from 'vitest'
2
- import { INTEGRATION_CAP_MARKERS } from '../cap-providers.js'
3
-
4
- describe('integration cap markers', () => {
5
- it('recognises device-adoption (not the old ha-discovery)', () => {
6
- expect(INTEGRATION_CAP_MARKERS.has('device-adoption')).toBe(true)
7
- expect(INTEGRATION_CAP_MARKERS.has('ha-discovery')).toBe(false)
8
- expect(INTEGRATION_CAP_MARKERS.has('device-provider')).toBe(true)
9
- })
10
- })
@@ -1,127 +0,0 @@
1
- /**
2
- * Addon settings router — raw DB proxy for the common settings API.
3
- *
4
- * Exposes four protected procedures consumed by:
5
- * 1. Forked addons (via the tRPC WSS client in `WorkerBootstrapService`)
6
- * to read/write their 3-level settings chain from the worker process.
7
- * 2. Future UI flows that want to inspect/mutate addon settings through
8
- * a single well-typed endpoint.
9
- *
10
- * The router is deliberately thin — it does NOT perform schema-based
11
- * resolver merging (defaults → global → per-device). That happens on the
12
- * consumer side where the addon's `ConfigUISchema` is available:
13
- * - In-process addons: handled by `SettingsResolverService.createView()`
14
- * wired into `AddonContext.settings` during `createAddonContext()`.
15
- * - Forked addons: handled by the `AddonSettingsView` constructed inside
16
- * `WorkerBootstrapService`, which has access to the worker's local
17
- * addon schema.
18
- *
19
- * Introduced in session 5 Sprint 3a (worker-bootstrap cap-aware wiring).
20
- */
21
- import { z } from 'zod'
22
- import type { ConfigService } from '../../core/config/config.service.js'
23
- import { trpcRouter, protectedProcedure } from '../trpc/trpc.middleware.js'
24
-
25
- const AddonSettingsRecordSchema = z.record(z.string(), z.unknown())
26
-
27
- const AddonIdInputSchema = z.object({
28
- addonId: z.string(),
29
- })
30
-
31
- const AddonDeviceInputSchema = z.object({
32
- addonId: z.string(),
33
- deviceId: z.string(),
34
- })
35
-
36
- const UpdateGlobalInputSchema = z.object({
37
- addonId: z.string(),
38
- field: z.string(),
39
- value: z.unknown(),
40
- })
41
-
42
- const UpdateDeviceInputSchema = z.object({
43
- addonId: z.string(),
44
- deviceId: z.string(),
45
- field: z.string(),
46
- value: z.unknown(),
47
- })
48
-
49
- const SuccessSchema = z.object({ success: z.literal(true) })
50
-
51
- const ReplaceGlobalInputSchema = z.object({
52
- addonId: z.string(),
53
- config: z.record(z.string(), z.unknown()),
54
- })
55
-
56
- export function createAddonSettingsRouter(cfg: ConfigService) {
57
- return trpcRouter({
58
- /**
59
- * Read the addon-global settings record for the given addon.
60
- * Returns the raw stored values (no defaults, no device overrides).
61
- */
62
- getGlobal: protectedProcedure
63
- .input(AddonIdInputSchema)
64
- .output(AddonSettingsRecordSchema)
65
- .query(({ input }) => cfg.getAddonConfig(input.addonId)),
66
-
67
- /**
68
- * Read the per-device override record for the given addon × device.
69
- * Returns the raw stored values (schema filtering happens on the
70
- * consumer side at merge time).
71
- */
72
- getDeviceOverrides: protectedProcedure
73
- .input(AddonDeviceInputSchema)
74
- .output(AddonSettingsRecordSchema)
75
- .query(({ input }) => cfg.getAddonDevice(input.addonId, input.deviceId)),
76
-
77
- /**
78
- * Update a single field in the addon-global settings record.
79
- * Reads the current record, merges the new value, and writes back
80
- * via `setAddonConfig` (bulk replace). Intended for small per-field
81
- * writes from addon code; bulk updates should use a dedicated admin
82
- * endpoint (not exposed here).
83
- */
84
- updateGlobal: protectedProcedure
85
- .input(UpdateGlobalInputSchema)
86
- .output(SuccessSchema)
87
- .mutation(({ input }) => {
88
- const current = cfg.getAddonConfig(input.addonId)
89
- cfg.setAddonConfig(input.addonId, { ...current, [input.field]: input.value })
90
- return { success: true as const }
91
- }),
92
-
93
- /**
94
- * Update a single field in the per-device override record for the
95
- * given addon × device. Merges with the existing overrides and
96
- * writes back via `setAddonDevice`. Scope enforcement (dropping
97
- * fields not declared as `scope: 'device'`) is the consumer's
98
- * responsibility — we preserve the raw shape at this layer so the
99
- * resolver contract remains symmetric with `getDeviceOverrides`.
100
- */
101
- updateDevice: protectedProcedure
102
- .input(UpdateDeviceInputSchema)
103
- .output(SuccessSchema)
104
- .mutation(({ input }) => {
105
- const current = cfg.getAddonDevice(input.addonId, input.deviceId)
106
- cfg.setAddonDevice(input.addonId, input.deviceId, {
107
- ...current,
108
- [input.field]: input.value,
109
- })
110
- return { success: true as const }
111
- }),
112
-
113
- /**
114
- * Replace the entire addon-global settings record in one call.
115
- * Used by forked workers for `context.config.setAll()`. Unlike
116
- * `updateGlobal` (single-field merge), this overwrites the full record.
117
- * Admin-level write: only workers with valid hub tokens can call this.
118
- */
119
- replaceGlobal: protectedProcedure
120
- .input(ReplaceGlobalInputSchema)
121
- .output(SuccessSchema)
122
- .mutation(({ input }) => {
123
- cfg.setAddonConfig(input.addonId, input.config)
124
- return { success: true as const }
125
- }),
126
- })
127
- }
@@ -1,86 +0,0 @@
1
- /**
2
- * Agents router — fixed core API (not a capability).
3
- *
4
- * Thin binding over AgentRegistryService which now delegates to Moleculer
5
- * for node discovery and health. Only role-assignment management and
6
- * node listing remain; protocol endpoints (register, heartbeat, task
7
- * dispatch) are handled natively by the Moleculer service mesh.
8
- */
9
- import { z } from 'zod'
10
- import type { AgentRegistryService } from '../../core/agent/agent-registry.service.js'
11
- import type { MoleculerService } from '../../core/moleculer/moleculer.service.js'
12
- import { trpcRouter, adminProcedure } from '../trpc/trpc.middleware.js'
13
-
14
- const AgentRoleSchema = z.enum(['decoder', 'transcoder', 'detector', 'recorder'])
15
-
16
- export function createAgentsRouter(ar: AgentRegistryService, moleculer: MoleculerService) {
17
- return trpcRouter({
18
- // ── Node listing (replaces listAgents / listConnected) ────────────
19
- listNodes: adminProcedure.input(z.void()).query(async () => {
20
- const items = await ar.listNodes()
21
- // Spread to mutable copies: AgentListItem uses readonly arrays; Zod output schema uses mutable.
22
- return items.map((a) => ({
23
- ...a,
24
- info: {
25
- ...a.info,
26
- capabilities: [...a.info.capabilities],
27
- },
28
- status: {
29
- ...a.status,
30
- fps: { ...a.status.fps },
31
- errors: [...a.status.errors],
32
- },
33
- subProcesses: [...a.subProcesses],
34
- }))
35
- }),
36
-
37
- // ── Capability discovery (via Moleculer service list) ─────────────
38
- getAgentCapabilities: adminProcedure.input(z.void()).query(async () => {
39
- // eslint-disable-next-line @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access -- moleculer.broker.call typing chain unresolvable; runtime shape validated by the cast below
40
- const services = (await moleculer.broker.call('$node.services', {})) as Array<{
41
- name: string
42
- }>
43
- const capSet = new Set<string>()
44
- for (const svc of services) {
45
- if (svc.name.startsWith('$')) continue
46
- capSet.add(svc.name)
47
- }
48
- return [...capSet].toSorted()
49
- }),
50
-
51
- // ── Role assignments ──────────────────────────────────────────────
52
- getAssignments: adminProcedure
53
- .input(z.object({ cameraId: z.number().optional() }))
54
- .query(({ input }) => ar.getAssignments(input.cameraId)),
55
-
56
- setAssignment: adminProcedure
57
- .input(
58
- z.object({
59
- cameraId: z.number(),
60
- role: AgentRoleSchema,
61
- agentId: z.string(),
62
- priority: z.enum(['primary', 'backup', 'overflow']),
63
- rtspUrl: z.string().optional(),
64
- }),
65
- )
66
- .mutation(({ input }) => ar.setAssignment(input as never)),
67
-
68
- removeAssignment: adminProcedure
69
- .input(
70
- z.object({
71
- cameraId: z.number(),
72
- role: AgentRoleSchema,
73
- }),
74
- )
75
- .mutation(({ input }) => ar.removeAssignment(input.cameraId, input.role as never)),
76
-
77
- activateBackup: adminProcedure
78
- .input(
79
- z.object({
80
- cameraId: z.number(),
81
- role: AgentRoleSchema,
82
- }),
83
- )
84
- .mutation(({ input }) => ar.activateBackup(input.cameraId, input.role as never)),
85
- })
86
- }