@byline/host-tanstack-start 5.0.0 → 5.1.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 (258) hide show
  1. package/dist/admin-shell/chrome/admin-app-bar.d.ts +7 -0
  2. package/dist/admin-shell/chrome/admin-app-bar.js +10 -2
  3. package/dist/admin-shell/chrome/optional-i18n-provider.d.ts +29 -0
  4. package/dist/admin-shell/chrome/optional-i18n-provider.js +24 -0
  5. package/dist/admin-shell/chrome/session-change-boundary.d.ts +14 -0
  6. package/dist/admin-shell/chrome/session-change-boundary.js +119 -0
  7. package/dist/admin-shell/chrome/session-change-boundary.module.js +11 -0
  8. package/dist/admin-shell/chrome/session-change-boundary.test.d.ts +8 -0
  9. package/dist/admin-shell/chrome/session-change-boundary_module.css +81 -0
  10. package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -1
  11. package/dist/admin-shell/chrome/sign-in-page.js +60 -3
  12. package/dist/integrations/byline-admin-services.js +7 -1
  13. package/dist/integrations/client-ip.d.ts +19 -0
  14. package/dist/integrations/client-ip.js +34 -0
  15. package/dist/integrations/session-coordination.d.ts +24 -0
  16. package/dist/integrations/session-coordination.js +85 -0
  17. package/dist/integrations/session-middleware.d.ts +11 -0
  18. package/dist/integrations/session-middleware.js +94 -0
  19. package/dist/integrations/session-request-kind.d.ts +9 -0
  20. package/dist/integrations/session-request-kind.js +10 -0
  21. package/dist/integrations/session-request-middleware.d.ts +9 -0
  22. package/dist/integrations/session-request-middleware.js +7 -0
  23. package/dist/integrations/sign-in-body.d.ts +12 -0
  24. package/dist/integrations/sign-in-body.js +52 -0
  25. package/dist/integrations/sign-in-middleware.d.ts +9 -0
  26. package/dist/integrations/sign-in-middleware.js +10 -0
  27. package/dist/routes/create-admin-layout-route.js +46 -31
  28. package/dist/routes/create-sign-in-route.js +4 -2
  29. package/dist/server-fns/admin-account/change-password.d.ts +3 -1
  30. package/dist/server-fns/admin-account/change-password.js +9 -3
  31. package/dist/server-fns/admin-account/get.d.ts +3 -1
  32. package/dist/server-fns/admin-account/get.js +4 -1
  33. package/dist/server-fns/admin-account/index.d.ts +5 -5
  34. package/dist/server-fns/admin-account/update.d.ts +3 -1
  35. package/dist/server-fns/admin-account/update.js +4 -1
  36. package/dist/server-fns/admin-activity/get.d.ts +3 -1
  37. package/dist/server-fns/admin-activity/get.js +4 -1
  38. package/dist/server-fns/admin-permissions/get-role-abilities.d.ts +3 -1
  39. package/dist/server-fns/admin-permissions/get-role-abilities.js +4 -1
  40. package/dist/server-fns/admin-permissions/index.d.ts +4 -4
  41. package/dist/server-fns/admin-permissions/list-registered.d.ts +3 -1
  42. package/dist/server-fns/admin-permissions/list-registered.js +4 -1
  43. package/dist/server-fns/admin-permissions/set-role-abilities.d.ts +3 -1
  44. package/dist/server-fns/admin-permissions/set-role-abilities.js +4 -1
  45. package/dist/server-fns/admin-permissions/who-has.d.ts +3 -1
  46. package/dist/server-fns/admin-permissions/who-has.js +4 -1
  47. package/dist/server-fns/admin-roles/create.d.ts +3 -1
  48. package/dist/server-fns/admin-roles/create.js +4 -1
  49. package/dist/server-fns/admin-roles/delete.d.ts +3 -1
  50. package/dist/server-fns/admin-roles/delete.js +4 -1
  51. package/dist/server-fns/admin-roles/get.d.ts +3 -1
  52. package/dist/server-fns/admin-roles/get.js +4 -1
  53. package/dist/server-fns/admin-roles/index.d.ts +6 -6
  54. package/dist/server-fns/admin-roles/list.d.ts +3 -1
  55. package/dist/server-fns/admin-roles/list.js +4 -1
  56. package/dist/server-fns/admin-roles/reorder.d.ts +3 -1
  57. package/dist/server-fns/admin-roles/reorder.js +4 -1
  58. package/dist/server-fns/admin-roles/update.d.ts +3 -1
  59. package/dist/server-fns/admin-roles/update.js +4 -1
  60. package/dist/server-fns/admin-users/create.d.ts +3 -1
  61. package/dist/server-fns/admin-users/create.js +4 -1
  62. package/dist/server-fns/admin-users/delete.d.ts +3 -1
  63. package/dist/server-fns/admin-users/delete.js +4 -1
  64. package/dist/server-fns/admin-users/disable.d.ts +3 -1
  65. package/dist/server-fns/admin-users/disable.js +4 -1
  66. package/dist/server-fns/admin-users/enable.d.ts +3 -1
  67. package/dist/server-fns/admin-users/enable.js +4 -1
  68. package/dist/server-fns/admin-users/get-user-roles.d.ts +3 -1
  69. package/dist/server-fns/admin-users/get-user-roles.js +4 -1
  70. package/dist/server-fns/admin-users/get.d.ts +3 -1
  71. package/dist/server-fns/admin-users/get.js +4 -1
  72. package/dist/server-fns/admin-users/index.d.ts +10 -10
  73. package/dist/server-fns/admin-users/list.d.ts +3 -1
  74. package/dist/server-fns/admin-users/list.js +4 -1
  75. package/dist/server-fns/admin-users/set-password.d.ts +3 -1
  76. package/dist/server-fns/admin-users/set-password.js +4 -1
  77. package/dist/server-fns/admin-users/set-user-roles.d.ts +3 -1
  78. package/dist/server-fns/admin-users/set-user-roles.js +4 -1
  79. package/dist/server-fns/admin-users/update.d.ts +3 -1
  80. package/dist/server-fns/admin-users/update.js +4 -1
  81. package/dist/server-fns/ai/execute.d.ts +3 -1
  82. package/dist/server-fns/ai/execute.js +4 -1
  83. package/dist/server-fns/ai/index.d.ts +1 -1
  84. package/dist/server-fns/analytics/maintenance.d.ts +6 -2
  85. package/dist/server-fns/analytics/maintenance.js +7 -2
  86. package/dist/server-fns/analytics/queries.d.ts +18 -6
  87. package/dist/server-fns/analytics/queries.js +19 -6
  88. package/dist/server-fns/auth/current-user.d.ts +1 -0
  89. package/dist/server-fns/auth/current-user.js +5 -2
  90. package/dist/server-fns/auth/renew.d.ts +13 -0
  91. package/dist/server-fns/auth/renew.js +45 -0
  92. package/dist/server-fns/auth/sign-in.d.ts +2 -1
  93. package/dist/server-fns/auth/sign-in.js +88 -17
  94. package/dist/server-fns/auth/sign-out.d.ts +3 -1
  95. package/dist/server-fns/auth/sign-out.js +38 -6
  96. package/dist/server-fns/collections/audit.d.ts +3 -1
  97. package/dist/server-fns/collections/audit.js +4 -1
  98. package/dist/server-fns/collections/copy-to-locale.d.ts +10 -1
  99. package/dist/server-fns/collections/copy-to-locale.js +5 -2
  100. package/dist/server-fns/collections/create.d.ts +10 -1
  101. package/dist/server-fns/collections/create.js +5 -2
  102. package/dist/server-fns/collections/delete-locale.d.ts +10 -1
  103. package/dist/server-fns/collections/delete-locale.js +5 -2
  104. package/dist/server-fns/collections/delete.d.ts +10 -1
  105. package/dist/server-fns/collections/delete.js +5 -2
  106. package/dist/server-fns/collections/duplicate.d.ts +10 -1
  107. package/dist/server-fns/collections/duplicate.js +5 -2
  108. package/dist/server-fns/collections/get.d.ts +7 -0
  109. package/dist/server-fns/collections/get.js +8 -3
  110. package/dist/server-fns/collections/history.d.ts +3 -1
  111. package/dist/server-fns/collections/history.js +4 -1
  112. package/dist/server-fns/collections/index.d.ts +21 -21
  113. package/dist/server-fns/collections/list.d.ts +10 -1
  114. package/dist/server-fns/collections/list.js +5 -2
  115. package/dist/server-fns/collections/reindex.d.ts +3 -1
  116. package/dist/server-fns/collections/reindex.js +4 -1
  117. package/dist/server-fns/collections/reorder.d.ts +10 -1
  118. package/dist/server-fns/collections/reorder.js +5 -2
  119. package/dist/server-fns/collections/restore-version.d.ts +10 -1
  120. package/dist/server-fns/collections/restore-version.js +5 -2
  121. package/dist/server-fns/collections/scheduled-publication.d.ts +22 -5
  122. package/dist/server-fns/collections/scheduled-publication.js +17 -6
  123. package/dist/server-fns/collections/set-list-view-preference.d.ts +3 -1
  124. package/dist/server-fns/collections/set-list-view-preference.js +4 -1
  125. package/dist/server-fns/collections/stats.js +4 -1
  126. package/dist/server-fns/collections/status.d.ts +13 -2
  127. package/dist/server-fns/collections/status.js +8 -3
  128. package/dist/server-fns/collections/tree.d.ts +22 -5
  129. package/dist/server-fns/collections/tree.js +17 -6
  130. package/dist/server-fns/collections/update.d.ts +13 -2
  131. package/dist/server-fns/collections/update.js +8 -3
  132. package/dist/server-fns/collections/upload.d.ts +10 -1
  133. package/dist/server-fns/collections/upload.js +4 -1
  134. package/dist/server-fns/i18n/set-locale.d.ts +3 -1
  135. package/dist/server-fns/i18n/set-locale.js +18 -3
  136. package/dist/server-fns/preview/disable.d.ts +3 -1
  137. package/dist/server-fns/preview/disable.js +4 -1
  138. package/dist/server-fns/preview/enable.d.ts +3 -1
  139. package/dist/server-fns/preview/enable.js +4 -1
  140. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +10 -1
  141. package/dist/server-fns/singletons/cancel-scheduled-publish.js +5 -2
  142. package/dist/server-fns/singletons/change-status.d.ts +10 -1
  143. package/dist/server-fns/singletons/change-status.js +5 -2
  144. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +10 -1
  145. package/dist/server-fns/singletons/confirm-scheduled-publish.js +5 -2
  146. package/dist/server-fns/singletons/copy-to-locale.d.ts +10 -1
  147. package/dist/server-fns/singletons/copy-to-locale.js +5 -2
  148. package/dist/server-fns/singletons/find-by-version.d.ts +3 -1
  149. package/dist/server-fns/singletons/find-by-version.js +4 -1
  150. package/dist/server-fns/singletons/get-scheduled-publish.d.ts +3 -1
  151. package/dist/server-fns/singletons/get-scheduled-publish.js +4 -1
  152. package/dist/server-fns/singletons/get.d.ts +3 -1
  153. package/dist/server-fns/singletons/get.js +4 -1
  154. package/dist/server-fns/singletons/history.d.ts +3 -1
  155. package/dist/server-fns/singletons/history.js +4 -1
  156. package/dist/server-fns/singletons/restore-version.d.ts +10 -1
  157. package/dist/server-fns/singletons/restore-version.js +5 -2
  158. package/dist/server-fns/singletons/schedule-publish.d.ts +10 -1
  159. package/dist/server-fns/singletons/schedule-publish.js +5 -2
  160. package/dist/server-fns/singletons/unpublish.d.ts +10 -1
  161. package/dist/server-fns/singletons/unpublish.js +5 -2
  162. package/dist/server-fns/singletons/update.d.ts +10 -1
  163. package/dist/server-fns/singletons/update.js +5 -2
  164. package/package.json +19 -19
  165. package/src/admin-shell/chrome/admin-app-bar.tsx +11 -6
  166. package/src/admin-shell/chrome/optional-i18n-provider.tsx +61 -0
  167. package/src/admin-shell/chrome/session-change-boundary.module.css +112 -0
  168. package/src/admin-shell/chrome/session-change-boundary.test.tsx +91 -0
  169. package/src/admin-shell/chrome/session-change-boundary.tsx +155 -0
  170. package/src/admin-shell/chrome/sign-in-page.tsx +68 -3
  171. package/src/integrations/byline-admin-services.ts +7 -1
  172. package/src/integrations/client-ip.test.node.ts +45 -0
  173. package/src/integrations/client-ip.ts +54 -0
  174. package/src/integrations/framework-fetch-shape.test.node.ts +56 -0
  175. package/src/integrations/session-client.test.node.ts +121 -0
  176. package/src/integrations/session-coordination.test.node.ts +77 -0
  177. package/src/integrations/session-coordination.ts +96 -0
  178. package/src/integrations/session-middleware.test.node.ts +87 -0
  179. package/src/integrations/session-middleware.ts +107 -0
  180. package/src/integrations/session-request-kind.ts +18 -0
  181. package/src/integrations/session-request-middleware.ts +17 -0
  182. package/src/integrations/sign-in-body.test.node.ts +59 -0
  183. package/src/integrations/sign-in-body.ts +50 -0
  184. package/src/integrations/sign-in-middleware.ts +24 -0
  185. package/src/routes/create-admin-layout-route.tsx +48 -30
  186. package/src/routes/create-sign-in-route.tsx +10 -2
  187. package/src/server-fns/admin-account/change-password.test.node.ts +69 -0
  188. package/src/server-fns/admin-account/change-password.ts +13 -2
  189. package/src/server-fns/admin-account/get.ts +2 -0
  190. package/src/server-fns/admin-account/update.ts +2 -0
  191. package/src/server-fns/admin-activity/get.ts +2 -0
  192. package/src/server-fns/admin-permissions/get-role-abilities.ts +2 -0
  193. package/src/server-fns/admin-permissions/list-registered.ts +5 -4
  194. package/src/server-fns/admin-permissions/set-role-abilities.ts +2 -0
  195. package/src/server-fns/admin-permissions/who-has.ts +2 -0
  196. package/src/server-fns/admin-roles/create.ts +2 -0
  197. package/src/server-fns/admin-roles/delete.ts +2 -0
  198. package/src/server-fns/admin-roles/get.ts +2 -0
  199. package/src/server-fns/admin-roles/list.ts +5 -4
  200. package/src/server-fns/admin-roles/reorder.ts +2 -0
  201. package/src/server-fns/admin-roles/update.ts +2 -0
  202. package/src/server-fns/admin-users/create.ts +2 -0
  203. package/src/server-fns/admin-users/delete.ts +2 -0
  204. package/src/server-fns/admin-users/disable.ts +2 -0
  205. package/src/server-fns/admin-users/enable.ts +2 -0
  206. package/src/server-fns/admin-users/get-user-roles.ts +2 -0
  207. package/src/server-fns/admin-users/get.ts +2 -0
  208. package/src/server-fns/admin-users/list.ts +2 -0
  209. package/src/server-fns/admin-users/set-password.ts +2 -0
  210. package/src/server-fns/admin-users/set-user-roles.ts +2 -0
  211. package/src/server-fns/admin-users/update.ts +2 -0
  212. package/src/server-fns/ai/execute.ts +3 -0
  213. package/src/server-fns/analytics/maintenance.ts +4 -0
  214. package/src/server-fns/analytics/queries.ts +14 -8
  215. package/src/server-fns/auth/current-user.ts +7 -3
  216. package/src/server-fns/auth/renew.test.node.ts +85 -0
  217. package/src/server-fns/auth/renew.ts +54 -0
  218. package/src/server-fns/auth/sign-in.test.node.ts +114 -0
  219. package/src/server-fns/auth/sign-in.ts +80 -27
  220. package/src/server-fns/auth/sign-out.test.node.ts +85 -0
  221. package/src/server-fns/auth/sign-out.ts +44 -19
  222. package/src/server-fns/collections/audit.ts +2 -0
  223. package/src/server-fns/collections/copy-to-locale.ts +3 -1
  224. package/src/server-fns/collections/create.ts +3 -1
  225. package/src/server-fns/collections/delete-locale.ts +3 -1
  226. package/src/server-fns/collections/delete.ts +3 -1
  227. package/src/server-fns/collections/duplicate.ts +3 -1
  228. package/src/server-fns/collections/get.ts +4 -1
  229. package/src/server-fns/collections/history.ts +2 -0
  230. package/src/server-fns/collections/list.ts +3 -1
  231. package/src/server-fns/collections/reindex.ts +2 -0
  232. package/src/server-fns/collections/reorder.ts +3 -1
  233. package/src/server-fns/collections/restore-version.ts +3 -1
  234. package/src/server-fns/collections/revision-input.test.node.ts +14 -0
  235. package/src/server-fns/collections/scheduled-publication.ts +14 -7
  236. package/src/server-fns/collections/set-list-view-preference.ts +2 -0
  237. package/src/server-fns/collections/stats.ts +2 -0
  238. package/src/server-fns/collections/status.ts +4 -1
  239. package/src/server-fns/collections/tree.ts +7 -1
  240. package/src/server-fns/collections/update.ts +4 -1
  241. package/src/server-fns/collections/upload-singleton.test.node.ts +8 -1
  242. package/src/server-fns/collections/upload.ts +10 -0
  243. package/src/server-fns/i18n/set-locale.ts +19 -3
  244. package/src/server-fns/preview/disable.ts +8 -4
  245. package/src/server-fns/preview/enable.ts +9 -5
  246. package/src/server-fns/singletons/cancel-scheduled-publish.ts +3 -1
  247. package/src/server-fns/singletons/change-status.ts +3 -1
  248. package/src/server-fns/singletons/confirm-scheduled-publish.ts +3 -1
  249. package/src/server-fns/singletons/copy-to-locale.ts +3 -1
  250. package/src/server-fns/singletons/find-by-version.ts +2 -0
  251. package/src/server-fns/singletons/get-scheduled-publish.ts +2 -0
  252. package/src/server-fns/singletons/get.ts +2 -0
  253. package/src/server-fns/singletons/history.ts +2 -0
  254. package/src/server-fns/singletons/restore-version.ts +3 -1
  255. package/src/server-fns/singletons/schedule-publish.ts +3 -1
  256. package/src/server-fns/singletons/singletons.test.node.ts +6 -0
  257. package/src/server-fns/singletons/unpublish.ts +3 -1
  258. package/src/server-fns/singletons/update.ts +3 -1
@@ -0,0 +1,59 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { describe, expect, it } from 'vitest'
10
+
11
+ import { checkSignInBody, SIGN_IN_MAX_BODY_BYTES, wasSignInBodyChecked } from './sign-in-body.js'
12
+
13
+ function request(body: BodyInit, headers: Record<string, string> = {}) {
14
+ return new Request('https://app.test/_serverFn/login', {
15
+ method: 'POST',
16
+ headers: { 'content-type': 'application/json', ...headers },
17
+ body,
18
+ duplex: 'half',
19
+ } as RequestInit)
20
+ }
21
+ describe('sign-in body boundary', () => {
22
+ it('preserves the original body for RPC parsing', async () => {
23
+ const req = request('{"data":{}}')
24
+ expect(await checkSignInBody(req)).toBeUndefined()
25
+ expect(wasSignInBodyChecked(req)).toBe(true)
26
+ expect(await req.json()).toEqual({ data: {} })
27
+ expect(wasSignInBodyChecked(request('{}'))).toBe(false)
28
+ })
29
+ it('rejects oversized declared or actual bodies', async () => {
30
+ expect(
31
+ (
32
+ await checkSignInBody(
33
+ request('{}', { 'content-length': String(SIGN_IN_MAX_BODY_BYTES + 1) })
34
+ )
35
+ )?.status
36
+ ).toBe(413)
37
+ const req = request('x'.repeat(SIGN_IN_MAX_BODY_BYTES + 1), { 'content-length': '1' })
38
+ expect((await checkSignInBody(req))?.status).toBe(413)
39
+ expect(wasSignInBodyChecked(req)).toBe(false)
40
+ })
41
+ it('counts chunked bodies without relying on Content-Length', async () => {
42
+ const req = request(
43
+ new ReadableStream({
44
+ start(controller) {
45
+ controller.enqueue(new Uint8Array(SIGN_IN_MAX_BODY_BYTES))
46
+ controller.enqueue(new Uint8Array(1))
47
+ controller.close()
48
+ },
49
+ })
50
+ )
51
+ expect((await checkSignInBody(req))?.status).toBe(413)
52
+ })
53
+ it('rejects multipart and wrong methods', async () => {
54
+ expect(
55
+ (await checkSignInBody(request('{}', { 'content-type': 'multipart/form-data' })))?.status
56
+ ).toBe(415)
57
+ expect((await checkSignInBody(new Request('https://app.test')))?.status).toBe(405)
58
+ })
59
+ })
@@ -0,0 +1,50 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ /** Limit the serialized RPC envelope, not just its eventual credential fields. */
10
+ export const SIGN_IN_MAX_BODY_BYTES = 16 * 1024
11
+ const checked = new WeakSet<Request>()
12
+
13
+ export function wasSignInBodyChecked(request: Request): boolean {
14
+ return checked.has(request)
15
+ }
16
+
17
+ /** Read a bounded clone before Start parses the original. Never await tee cancellation. */
18
+ export async function checkSignInBody(request: Request): Promise<Response | undefined> {
19
+ if (request.method !== 'POST')
20
+ return new Response('Method not allowed', { status: 405, headers: { Allow: 'POST' } })
21
+ if (!request.headers.get('content-type')?.toLowerCase().startsWith('application/json')) {
22
+ return new Response('Expected JSON', { status: 415 })
23
+ }
24
+ const declared = request.headers.get('content-length')
25
+ if (declared !== null && (!/^\d+$/.test(declared) || Number(declared) > SIGN_IN_MAX_BODY_BYTES)) {
26
+ return new Response('Payload too large', { status: 413 })
27
+ }
28
+ const reader = request.clone().body?.getReader()
29
+ if (!reader) return new Response('Missing body', { status: 400 })
30
+ let timer: ReturnType<typeof setTimeout> | undefined
31
+ const deadline = new Promise<never>((_, reject) => {
32
+ timer = setTimeout(() => reject(new Error('Body timeout')), 5000)
33
+ })
34
+ try {
35
+ let size = 0
36
+ while (true) {
37
+ const { value, done } = await Promise.race([reader.read(), deadline])
38
+ if (done) break
39
+ size += value.byteLength
40
+ if (size > SIGN_IN_MAX_BODY_BYTES) return new Response('Payload too large', { status: 413 })
41
+ }
42
+ checked.add(request)
43
+ return undefined
44
+ } catch {
45
+ return new Response('Incomplete request body', { status: 408 })
46
+ } finally {
47
+ clearTimeout(timer)
48
+ void reader.cancel().catch(() => {})
49
+ }
50
+ }
@@ -0,0 +1,24 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ import { createMiddleware } from '@tanstack/react-start'
10
+
11
+ import { adminSignIn } from '../server-fns/auth/sign-in.js'
12
+ import { checkSignInBody } from './sign-in-body.js'
13
+
14
+ /** Register after CSRF in Start's global requestMiddleware, before RPC deserialization. */
15
+ export const passwordSignInMiddleware = createMiddleware().server(async (ctx) => {
16
+ const endpoint = new URL(adminSignIn.url, ctx.request.url).pathname.replace(/\/$/, '')
17
+ if (
18
+ ctx.handlerType !== 'serverFn' ||
19
+ (ctx.pathname !== endpoint && !ctx.pathname.startsWith(`${endpoint}/`))
20
+ )
21
+ return ctx.next()
22
+ const rejected = await checkSignInBody(ctx.request)
23
+ return rejected ?? ctx.next()
24
+ })
@@ -10,8 +10,8 @@
10
10
  * Admin shell layout — authenticated-only.
11
11
  *
12
12
  * `beforeLoad` resolves the current admin user (which internally verifies
13
- * the session cookies, refreshes transparently when needed, and throws
14
- * `ERR_UNAUTHENTICATED` when there is no session). On unauthenticated
13
+ * the access credential without rotating cookies). Expired access is
14
+ * renewed by the sign-in bootstrap. On unauthenticated
15
15
  * requests we redirect to `/sign-in` with a `callbackUrl` so the user
16
16
  * lands back on the page they originally requested.
17
17
  *
@@ -37,10 +37,12 @@ import { AdminMenuProvider } from '../admin-shell/chrome/admin-menu-provider.jsx
37
37
  import { Content } from '../admin-shell/chrome/content.js'
38
38
  import { RouteError, RouteNotFound } from '../admin-shell/chrome/route-error.js'
39
39
  import { RouteProgressBar } from '../admin-shell/chrome/route-progress-bar.js'
40
+ import { SessionChangeBoundary } from '../admin-shell/chrome/session-change-boundary.js'
40
41
  import { buildLocaleDefinitions } from '../i18n/locale-definitions.js'
41
42
  import { bylineAdminServices } from '../integrations/byline-admin-services.js'
42
43
  import { BylineAiAdminProvider } from '../integrations/byline-ai.js'
43
44
  import { bylineFieldServices } from '../integrations/byline-field-services.js'
45
+ import { observeSession, sessionIsChanged } from '../integrations/session-coordination.js'
44
46
  import { getAnalyticsRuntime } from '../server-fns/analytics/index.js'
45
47
  import { getCurrentAdminUser } from '../server-fns/auth/index.js'
46
48
  import { getScheduledPublicationRuntime } from '../server-fns/collections/index.js'
@@ -53,6 +55,7 @@ export function createAdminLayoutRoute(path: string) {
53
55
  const signInPath = getSignInRoutePath()
54
56
  try {
55
57
  const user = await getCurrentAdminUser()
58
+ if (typeof window !== 'undefined') observeSession(user.sessionId)
56
59
  // Resolve the active interface locale once on the server so SSR
57
60
  // and the hydrated client render the same translations and
58
61
  // there's no locale flicker. Going through the server fn (rather
@@ -65,7 +68,20 @@ export function createAdminLayoutRoute(path: string) {
65
68
  getAnalyticsRuntime(),
66
69
  ])
67
70
  return { user, activeLocale, scheduledPublicationRuntime, analyticsRuntime }
68
- } catch {
71
+ } catch (error) {
72
+ const code = (error as { code?: string })?.code
73
+ if (
74
+ !(typeof window !== 'undefined' && sessionIsChanged()) &&
75
+ ![
76
+ 'ERR_UNAUTHENTICATED',
77
+ 'ERR_ACCESS_EXPIRED',
78
+ 'ERR_INVALID_TOKEN',
79
+ 'ERR_REVOKED_TOKEN',
80
+ 'ERR_ACCOUNT_DISABLED',
81
+ 'ERR_SESSION_CHANGED',
82
+ ].includes(code ?? '')
83
+ )
84
+ throw error
69
85
  // `getCurrentAdminUser` (via `getAdminRequestContext`) throws
70
86
  // `ERR_UNAUTHENTICATED` or a related auth error when no valid
71
87
  // session is present. Redirect to sign-in with the original path
@@ -104,33 +120,35 @@ export function createAdminLayoutRoute(path: string) {
104
120
  window.location.reload()
105
121
  }
106
122
  return (
107
- <I18nProvider
108
- bundle={i18n.translations ?? {}}
109
- activeLocale={activeLocale}
110
- defaultLocale={i18n.admin.defaultLocale}
111
- localeDefinitions={localeDefinitions}
112
- setLocale={handleSetLocale}
113
- >
114
- <BylineAdminServicesProvider services={bylineAdminServices}>
115
- <BylineFieldServicesProvider services={bylineFieldServices}>
116
- <BylineAiAdminProvider>
117
- <AdminMenuProvider>
118
- <RouteProgressBar />
119
- <AdminAppBar user={user} />
120
- <main className={cx('byline-admin-layout-main', layoutStyles.main)}>
121
- <AdminMenuDrawer
122
- analyticsEnabled={analyticsRuntime.enabled}
123
- scheduledPublicationEnabled={scheduledPublicationRuntime.enabled}
124
- />
125
- <Content>
126
- <Outlet />
127
- </Content>
128
- </main>
129
- </AdminMenuProvider>
130
- </BylineAiAdminProvider>
131
- </BylineFieldServicesProvider>
132
- </BylineAdminServicesProvider>
133
- </I18nProvider>
123
+ <SessionChangeBoundary user={user}>
124
+ <I18nProvider
125
+ bundle={i18n.translations ?? {}}
126
+ activeLocale={activeLocale}
127
+ defaultLocale={i18n.admin.defaultLocale}
128
+ localeDefinitions={localeDefinitions}
129
+ setLocale={handleSetLocale}
130
+ >
131
+ <BylineAdminServicesProvider services={bylineAdminServices}>
132
+ <BylineFieldServicesProvider services={bylineFieldServices}>
133
+ <BylineAiAdminProvider>
134
+ <AdminMenuProvider>
135
+ <RouteProgressBar />
136
+ <AdminAppBar user={user} />
137
+ <main className={cx('byline-admin-layout-main', layoutStyles.main)}>
138
+ <AdminMenuDrawer
139
+ analyticsEnabled={analyticsRuntime.enabled}
140
+ scheduledPublicationEnabled={scheduledPublicationRuntime.enabled}
141
+ />
142
+ <Content>
143
+ <Outlet />
144
+ </Content>
145
+ </main>
146
+ </AdminMenuProvider>
147
+ </BylineAiAdminProvider>
148
+ </BylineFieldServicesProvider>
149
+ </BylineAdminServicesProvider>
150
+ </I18nProvider>
151
+ </SessionChangeBoundary>
134
152
  )
135
153
  },
136
154
  errorComponent: RouteError,
@@ -25,6 +25,7 @@ import { resolveAdminCallbackPath, resolveAdminSignInRedirect } from './admin-pa
25
25
  import { resolveSignInHomeUrl } from './sign-in-home-url.js'
26
26
 
27
27
  interface SignInSearch {
28
+ reauthenticate?: boolean
28
29
  callbackUrl?: string
29
30
  }
30
31
 
@@ -42,7 +43,13 @@ export function createSignInRoute(path: string, { homeUrl }: CreateSignInRouteOp
42
43
  const Route: any = createFileRoute(path as never)({
43
44
  validateSearch: (search: Record<string, unknown>): SignInSearch => {
44
45
  const callbackUrl = resolveAdminCallbackPath(search.callbackUrl)
45
- return { callbackUrl }
46
+ return {
47
+ callbackUrl,
48
+ reauthenticate:
49
+ search.reauthenticate === '1' ||
50
+ search.reauthenticate === 1 ||
51
+ search.reauthenticate === true,
52
+ }
46
53
  },
47
54
  beforeLoad: async () => {
48
55
  // Resolve the active locale on the server before render so the
@@ -54,11 +61,12 @@ export function createSignInRoute(path: string, { homeUrl }: CreateSignInRouteOp
54
61
  return { activeLocale }
55
62
  },
56
63
  component: function SignInRouteComponent() {
57
- const { callbackUrl } = Route.useSearch() as SignInSearch
64
+ const { callbackUrl, reauthenticate } = Route.useSearch() as SignInSearch
58
65
  const { activeLocale } = Route.useRouteContext() as { activeLocale: LocaleCode }
59
66
  return (
60
67
  <SignInPage
61
68
  redirectTo={resolveAdminSignInRedirect(callbackUrl)}
69
+ reauthenticate={reauthenticate}
62
70
  activeLocale={activeLocale}
63
71
  homeUrl={resolvedHomeUrl}
64
72
  />
@@ -0,0 +1,69 @@
1
+ /**
2
+ * This Source Code is subject to the terms of the Mozilla Public
3
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
4
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
5
+ *
6
+ * Copyright (c) Infonomic Company Limited
7
+ */
8
+
9
+ // Handler-level tests isolate transport; session-middleware tests exercise the real pre-handler boundary.
10
+ vi.mock('../../integrations/session-middleware.js', () => ({ adminSessionMiddleware: {} }))
11
+
12
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
13
+
14
+ const mocks = vi.hoisted(() => ({
15
+ context: vi.fn(),
16
+ command: vi.fn(),
17
+ clear: vi.fn(),
18
+ preview: vi.fn(),
19
+ }))
20
+ vi.mock('@tanstack/react-start', () => ({
21
+ createServerFn: () => {
22
+ const chain = {
23
+ middleware: () => chain,
24
+ validator: () => ({ handler: (handler: unknown) => handler }),
25
+ }
26
+ return chain
27
+ },
28
+ }))
29
+ vi.mock('@byline/admin/admin-account', () => ({ changeAccountPasswordCommand: mocks.command }))
30
+ vi.mock('@byline/client/server', () => ({
31
+ getAdminRequestContext: mocks.context,
32
+ clearSessionCookies: mocks.clear,
33
+ clearPreviewCookie: mocks.preview,
34
+ }))
35
+ vi.mock('../../integrations/byline-core.js', () => ({ bylineCore: () => ({ adminStore: {} }) }))
36
+
37
+ import { changeAccountPassword } from './change-password.js'
38
+
39
+ const call = changeAccountPassword as unknown as (args: { data: unknown }) => Promise<unknown>
40
+ beforeEach(() => {
41
+ vi.resetAllMocks()
42
+ mocks.context.mockResolvedValue({ actor: { id: 'current' } })
43
+ })
44
+ describe('password change cookie policy', () => {
45
+ it('clears the changing session and preview only after the mutation succeeds', async () => {
46
+ mocks.command.mockImplementation(async () => {
47
+ expect(mocks.clear).not.toHaveBeenCalled()
48
+ return { id: 'current', vid: 2 }
49
+ })
50
+ await expect(call({ data: {} })).resolves.toEqual({ id: 'current', vid: 2 })
51
+ expect(mocks.clear).toHaveBeenCalledOnce()
52
+ expect(mocks.preview).toHaveBeenCalledOnce()
53
+ })
54
+ it.each(['current password', 'stale revision', 'database rollback'])(
55
+ 'does not clear cookies after %s failure',
56
+ async (reason) => {
57
+ mocks.command.mockRejectedValue(new Error(reason))
58
+ await expect(call({ data: {} })).rejects.toThrow(reason)
59
+ expect(mocks.clear).not.toHaveBeenCalled()
60
+ expect(mocks.preview).not.toHaveBeenCalled()
61
+ }
62
+ )
63
+ it('does not mutate or clear cookies when authentication fails', async () => {
64
+ mocks.context.mockRejectedValue(new Error('unauthenticated'))
65
+ await expect(call({ data: {} })).rejects.toThrow('unauthenticated')
66
+ expect(mocks.command).not.toHaveBeenCalled()
67
+ expect(mocks.clear).not.toHaveBeenCalled()
68
+ })
69
+ })
@@ -9,9 +9,14 @@
9
9
  import { createServerFn } from '@tanstack/react-start'
10
10
 
11
11
  import { type AccountResponse, changeAccountPasswordCommand } from '@byline/admin/admin-account'
12
- import { getAdminRequestContext } from '@byline/client/server'
12
+ import {
13
+ clearPreviewCookie,
14
+ clearSessionCookies,
15
+ getAdminRequestContext,
16
+ } from '@byline/client/server'
13
17
 
14
18
  import { bylineCore } from '../../integrations/byline-core.js'
19
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
20
 
16
21
  export interface ChangeAccountPasswordInput {
17
22
  vid: number
@@ -20,8 +25,14 @@ export interface ChangeAccountPasswordInput {
20
25
  }
21
26
 
22
27
  export const changeAccountPassword = createServerFn({ method: 'POST' })
28
+ .middleware([adminSessionMiddleware])
23
29
  .validator((input: ChangeAccountPasswordInput) => input)
24
30
  .handler(async ({ data }): Promise<AccountResponse> => {
25
31
  const context = await getAdminRequestContext()
26
- return changeAccountPasswordCommand(context, data, { store: bylineCore().adminStore! })
32
+ const account = await changeAccountPasswordCommand(context, data, {
33
+ store: bylineCore().adminStore!,
34
+ })
35
+ clearSessionCookies()
36
+ clearPreviewCookie()
37
+ return account
27
38
  })
@@ -12,6 +12,7 @@ import { type AccountResponse, getAccountCommand } from '@byline/admin/admin-acc
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  /**
17
18
  * Read the currently signed-in admin's full account row. Used by the
@@ -20,6 +21,7 @@ import { bylineCore } from '../../integrations/byline-core.js'
20
21
  * doesn't carry.
21
22
  */
22
23
  export const getAccount = createServerFn({ method: 'GET' })
24
+ .middleware([adminSessionMiddleware])
23
25
  .validator((input?: Record<string, never>) => input ?? {})
24
26
  .handler(async ({ data }): Promise<AccountResponse> => {
25
27
  const context = await getAdminRequestContext()
@@ -12,6 +12,7 @@ import { type AccountResponse, updateAccountCommand } from '@byline/admin/admin-
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  export interface UpdateAccountInput {
17
18
  vid: number
@@ -24,6 +25,7 @@ export interface UpdateAccountInput {
24
25
  }
25
26
 
26
27
  export const updateAccount = createServerFn({ method: 'POST' })
28
+ .middleware([adminSessionMiddleware])
27
29
  .validator((input: UpdateAccountInput) => input)
28
30
  .handler(async ({ data }): Promise<AccountResponse> => {
29
31
  const context = await getAdminRequestContext()
@@ -13,6 +13,7 @@ import { ADMIN_ACTIVITY_ABILITIES } from '@byline/admin/admin-activity'
13
13
  import { getAdminRequestContext } from '@byline/client/server'
14
14
  import { ERR_AUDIT_UNSUPPORTED, getLogger, getServerConfig } from '@byline/core'
15
15
 
16
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
16
17
  import { type ActorLabelMap, resolveActorLabels } from '../collections/actors.js'
17
18
  import type { AuditLogEntryDto } from '../collections/audit.js'
18
19
 
@@ -59,6 +60,7 @@ export interface SystemActivityResponse {
59
60
  * than by any document's own read gate.
60
61
  */
61
62
  export const getSystemActivityLog = createServerFn({ method: 'GET' })
63
+ .middleware([adminSessionMiddleware])
62
64
  .validator((input: SystemActivitySearchParams) => input ?? {})
63
65
  .handler(async ({ data }): Promise<SystemActivityResponse> => {
64
66
  const context = await getAdminRequestContext()
@@ -15,8 +15,10 @@ import {
15
15
  import { getAdminRequestContext } from '@byline/client/server'
16
16
 
17
17
  import { bylineCore } from '../../integrations/byline-core.js'
18
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
18
19
 
19
20
  export const getRoleAbilities = createServerFn({ method: 'GET' })
21
+ .middleware([adminSessionMiddleware])
20
22
  .validator((input: { id: string }) => input)
21
23
  .handler(async ({ data }): Promise<GetRoleAbilitiesResponse> => {
22
24
  const context = await getAdminRequestContext()
@@ -15,14 +15,16 @@ import {
15
15
  import { getAdminRequestContext } from '@byline/client/server'
16
16
 
17
17
  import { bylineCore } from '../../integrations/byline-core.js'
18
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
18
19
 
19
20
  /**
20
21
  * List every registered ability — fed from `bylineCore().abilities`,
21
22
  * populated at init time by collection auto-registration plus
22
23
  * `registerAdminAbilities`. No DB read.
23
24
  */
24
- export const listRegisteredAbilities = createServerFn({ method: 'GET' }).handler(
25
- async (): Promise<ListRegisteredAbilitiesResponse> => {
25
+ export const listRegisteredAbilities = createServerFn({ method: 'GET' })
26
+ .middleware([adminSessionMiddleware])
27
+ .handler(async (): Promise<ListRegisteredAbilitiesResponse> => {
26
28
  const context = await getAdminRequestContext()
27
29
  return listRegisteredAbilitiesCommand(
28
30
  context,
@@ -32,5 +34,4 @@ export const listRegisteredAbilities = createServerFn({ method: 'GET' }).handler
32
34
  abilities: bylineCore().abilities,
33
35
  }
34
36
  )
35
- }
36
- )
37
+ })
@@ -15,6 +15,7 @@ import {
15
15
  import { getAdminRequestContext } from '@byline/client/server'
16
16
 
17
17
  import { bylineCore } from '../../integrations/byline-core.js'
18
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
18
19
 
19
20
  export interface SetRoleAbilitiesInput {
20
21
  id: string
@@ -22,6 +23,7 @@ export interface SetRoleAbilitiesInput {
22
23
  }
23
24
 
24
25
  export const setRoleAbilities = createServerFn({ method: 'POST' })
26
+ .middleware([adminSessionMiddleware])
25
27
  .validator((input: SetRoleAbilitiesInput) => input)
26
28
  .handler(async ({ data }): Promise<SetRoleAbilitiesResponse> => {
27
29
  const context = await getAdminRequestContext()
@@ -12,8 +12,10 @@ import { type WhoHasAbilityResponse, whoHasAbilityCommand } from '@byline/admin/
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  export const whoHasAbility = createServerFn({ method: 'GET' })
18
+ .middleware([adminSessionMiddleware])
17
19
  .validator((input: { ability: string }) => input)
18
20
  .handler(async ({ data }): Promise<WhoHasAbilityResponse> => {
19
21
  const context = await getAdminRequestContext()
@@ -12,6 +12,7 @@ import { type AdminRoleResponse, createAdminRoleCommand } from '@byline/admin/ad
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  export interface CreateAdminRoleInput {
17
18
  name: string
@@ -21,6 +22,7 @@ export interface CreateAdminRoleInput {
21
22
  }
22
23
 
23
24
  export const createAdminRole = createServerFn({ method: 'POST' })
25
+ .middleware([adminSessionMiddleware])
24
26
  .validator((input: CreateAdminRoleInput) => input)
25
27
  .handler(async ({ data }): Promise<AdminRoleResponse> => {
26
28
  const context = await getAdminRequestContext()
@@ -12,12 +12,14 @@ import { deleteAdminRoleCommand } from '@byline/admin/admin-roles'
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  // Return shape mirrors `OkResponse` from `@byline/admin/admin-users`.
17
18
  // Lifting the type to a shared spot is a small follow-up — the two
18
19
  // modules each declare their own identical `OkResponse` today, and the
19
20
  // root `@byline/admin` barrel can only re-export one of them.
20
21
  export const deleteAdminRole = createServerFn({ method: 'POST' })
22
+ .middleware([adminSessionMiddleware])
21
23
  .validator((input: { id: string; vid: number }) => input)
22
24
  .handler(async ({ data }): Promise<{ ok: true }> => {
23
25
  const context = await getAdminRequestContext()
@@ -12,8 +12,10 @@ import { type AdminRoleResponse, getAdminRoleCommand } from '@byline/admin/admin
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  export const getAdminRole = createServerFn({ method: 'GET' })
18
+ .middleware([adminSessionMiddleware])
17
19
  .validator((input: { id: string }) => input)
18
20
  .handler(async ({ data }): Promise<AdminRoleResponse> => {
19
21
  const context = await getAdminRequestContext()
@@ -12,6 +12,7 @@ import { type AdminRoleListResponse, listAdminRolesCommand } from '@byline/admin
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  /**
17
18
  * List admin roles. Thin server-fn wrapper over `listAdminRolesCommand`
@@ -19,9 +20,9 @@ import { bylineCore } from '../../integrations/byline-core.js'
19
20
  * No paging or sorting at this layer; the role set is small by design
20
21
  * and ordered by the `order` column.
21
22
  */
22
- export const listAdminRoles = createServerFn({ method: 'GET' }).handler(
23
- async (): Promise<AdminRoleListResponse> => {
23
+ export const listAdminRoles = createServerFn({ method: 'GET' })
24
+ .middleware([adminSessionMiddleware])
25
+ .handler(async (): Promise<AdminRoleListResponse> => {
24
26
  const context = await getAdminRequestContext()
25
27
  return listAdminRolesCommand(context, {}, { store: bylineCore().adminStore! })
26
- }
27
- )
28
+ })
@@ -12,8 +12,10 @@ import { reorderAdminRolesCommand } from '@byline/admin/admin-roles'
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  export const reorderAdminRoles = createServerFn({ method: 'POST' })
18
+ .middleware([adminSessionMiddleware])
17
19
  .validator((input: { ids: string[] }) => input)
18
20
  .handler(async ({ data }): Promise<{ ok: true }> => {
19
21
  const context = await getAdminRequestContext()
@@ -12,6 +12,7 @@ import { type AdminRoleResponse, updateAdminRoleCommand } from '@byline/admin/ad
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  export interface UpdateAdminRoleInput {
17
18
  id: string
@@ -24,6 +25,7 @@ export interface UpdateAdminRoleInput {
24
25
  }
25
26
 
26
27
  export const updateAdminRole = createServerFn({ method: 'POST' })
28
+ .middleware([adminSessionMiddleware])
27
29
  .validator((input: UpdateAdminRoleInput) => input)
28
30
  .handler(async ({ data }): Promise<AdminRoleResponse> => {
29
31
  const context = await getAdminRequestContext()
@@ -12,6 +12,7 @@ import { type AdminUserResponse, createAdminUserCommand } from '@byline/admin/ad
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  export interface CreateAdminUserInput {
17
18
  email: string
@@ -25,6 +26,7 @@ export interface CreateAdminUserInput {
25
26
  }
26
27
 
27
28
  export const createAdminUser = createServerFn({ method: 'POST' })
29
+ .middleware([adminSessionMiddleware])
28
30
  .validator((input: CreateAdminUserInput) => input)
29
31
  .handler(async ({ data }): Promise<AdminUserResponse> => {
30
32
  const context = await getAdminRequestContext()
@@ -12,8 +12,10 @@ import { deleteAdminUserCommand, type OkResponse } from '@byline/admin/admin-use
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  export const deleteAdminUser = createServerFn({ method: 'POST' })
18
+ .middleware([adminSessionMiddleware])
17
19
  .validator((input: { id: string; vid: number }) => input)
18
20
  .handler(async ({ data }): Promise<OkResponse> => {
19
21
  const context = await getAdminRequestContext()
@@ -12,11 +12,13 @@ import { disableAdminUserCommand, type OkResponse } from '@byline/admin/admin-us
12
12
  import { getAdminRequestContext } from '@byline/client/server'
13
13
 
14
14
  import { bylineCore } from '../../integrations/byline-core.js'
15
+ import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
15
16
 
16
17
  // No UI affordance yet — paired with `enable.ts`, both ship ahead of the
17
18
  // detail-view enable/disable control they will be wired into. Keep until
18
19
  // that control lands; not dead code.
19
20
  export const disableAdminUser = createServerFn({ method: 'POST' })
21
+ .middleware([adminSessionMiddleware])
20
22
  .validator((input: { id: string }) => input)
21
23
  .handler(async ({ data }): Promise<OkResponse> => {
22
24
  const context = await getAdminRequestContext()