@byline/host-tanstack-start 5.0.0 → 5.1.0
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/dist/admin-shell/chrome/admin-app-bar.d.ts +7 -0
- package/dist/admin-shell/chrome/admin-app-bar.js +10 -2
- package/dist/admin-shell/chrome/session-change-boundary.d.ts +14 -0
- package/dist/admin-shell/chrome/session-change-boundary.js +87 -0
- package/dist/admin-shell/chrome/session-change-boundary.test.d.ts +8 -0
- package/dist/admin-shell/chrome/sign-in-page.d.ts +2 -1
- package/dist/admin-shell/chrome/sign-in-page.js +60 -3
- package/dist/integrations/byline-admin-services.js +7 -1
- package/dist/integrations/client-ip.d.ts +19 -0
- package/dist/integrations/client-ip.js +34 -0
- package/dist/integrations/session-coordination.d.ts +24 -0
- package/dist/integrations/session-coordination.js +85 -0
- package/dist/integrations/session-middleware.d.ts +11 -0
- package/dist/integrations/session-middleware.js +94 -0
- package/dist/integrations/session-request-kind.d.ts +9 -0
- package/dist/integrations/session-request-kind.js +10 -0
- package/dist/integrations/session-request-middleware.d.ts +9 -0
- package/dist/integrations/session-request-middleware.js +7 -0
- package/dist/integrations/sign-in-body.d.ts +12 -0
- package/dist/integrations/sign-in-body.js +52 -0
- package/dist/integrations/sign-in-middleware.d.ts +9 -0
- package/dist/integrations/sign-in-middleware.js +10 -0
- package/dist/routes/create-admin-layout-route.js +46 -31
- package/dist/routes/create-sign-in-route.js +4 -2
- package/dist/server-fns/admin-account/change-password.d.ts +3 -1
- package/dist/server-fns/admin-account/change-password.js +9 -3
- package/dist/server-fns/admin-account/get.d.ts +3 -1
- package/dist/server-fns/admin-account/get.js +4 -1
- package/dist/server-fns/admin-account/index.d.ts +5 -5
- package/dist/server-fns/admin-account/update.d.ts +3 -1
- package/dist/server-fns/admin-account/update.js +4 -1
- package/dist/server-fns/admin-activity/get.d.ts +3 -1
- package/dist/server-fns/admin-activity/get.js +4 -1
- package/dist/server-fns/admin-permissions/get-role-abilities.d.ts +3 -1
- package/dist/server-fns/admin-permissions/get-role-abilities.js +4 -1
- package/dist/server-fns/admin-permissions/index.d.ts +4 -4
- package/dist/server-fns/admin-permissions/list-registered.d.ts +3 -1
- package/dist/server-fns/admin-permissions/list-registered.js +4 -1
- package/dist/server-fns/admin-permissions/set-role-abilities.d.ts +3 -1
- package/dist/server-fns/admin-permissions/set-role-abilities.js +4 -1
- package/dist/server-fns/admin-permissions/who-has.d.ts +3 -1
- package/dist/server-fns/admin-permissions/who-has.js +4 -1
- package/dist/server-fns/admin-roles/create.d.ts +3 -1
- package/dist/server-fns/admin-roles/create.js +4 -1
- package/dist/server-fns/admin-roles/delete.d.ts +3 -1
- package/dist/server-fns/admin-roles/delete.js +4 -1
- package/dist/server-fns/admin-roles/get.d.ts +3 -1
- package/dist/server-fns/admin-roles/get.js +4 -1
- package/dist/server-fns/admin-roles/index.d.ts +6 -6
- package/dist/server-fns/admin-roles/list.d.ts +3 -1
- package/dist/server-fns/admin-roles/list.js +4 -1
- package/dist/server-fns/admin-roles/reorder.d.ts +3 -1
- package/dist/server-fns/admin-roles/reorder.js +4 -1
- package/dist/server-fns/admin-roles/update.d.ts +3 -1
- package/dist/server-fns/admin-roles/update.js +4 -1
- package/dist/server-fns/admin-users/create.d.ts +3 -1
- package/dist/server-fns/admin-users/create.js +4 -1
- package/dist/server-fns/admin-users/delete.d.ts +3 -1
- package/dist/server-fns/admin-users/delete.js +4 -1
- package/dist/server-fns/admin-users/disable.d.ts +3 -1
- package/dist/server-fns/admin-users/disable.js +4 -1
- package/dist/server-fns/admin-users/enable.d.ts +3 -1
- package/dist/server-fns/admin-users/enable.js +4 -1
- package/dist/server-fns/admin-users/get-user-roles.d.ts +3 -1
- package/dist/server-fns/admin-users/get-user-roles.js +4 -1
- package/dist/server-fns/admin-users/get.d.ts +3 -1
- package/dist/server-fns/admin-users/get.js +4 -1
- package/dist/server-fns/admin-users/index.d.ts +10 -10
- package/dist/server-fns/admin-users/list.d.ts +3 -1
- package/dist/server-fns/admin-users/list.js +4 -1
- package/dist/server-fns/admin-users/set-password.d.ts +3 -1
- package/dist/server-fns/admin-users/set-password.js +4 -1
- package/dist/server-fns/admin-users/set-user-roles.d.ts +3 -1
- package/dist/server-fns/admin-users/set-user-roles.js +4 -1
- package/dist/server-fns/admin-users/update.d.ts +3 -1
- package/dist/server-fns/admin-users/update.js +4 -1
- package/dist/server-fns/ai/execute.d.ts +3 -1
- package/dist/server-fns/ai/execute.js +4 -1
- package/dist/server-fns/ai/index.d.ts +1 -1
- package/dist/server-fns/analytics/maintenance.d.ts +6 -2
- package/dist/server-fns/analytics/maintenance.js +7 -2
- package/dist/server-fns/analytics/queries.d.ts +18 -6
- package/dist/server-fns/analytics/queries.js +19 -6
- package/dist/server-fns/auth/current-user.d.ts +1 -0
- package/dist/server-fns/auth/current-user.js +5 -2
- package/dist/server-fns/auth/renew.d.ts +13 -0
- package/dist/server-fns/auth/renew.js +45 -0
- package/dist/server-fns/auth/sign-in.d.ts +2 -1
- package/dist/server-fns/auth/sign-in.js +88 -17
- package/dist/server-fns/auth/sign-out.d.ts +3 -1
- package/dist/server-fns/auth/sign-out.js +38 -6
- package/dist/server-fns/collections/audit.d.ts +3 -1
- package/dist/server-fns/collections/audit.js +4 -1
- package/dist/server-fns/collections/copy-to-locale.d.ts +10 -1
- package/dist/server-fns/collections/copy-to-locale.js +5 -2
- package/dist/server-fns/collections/create.d.ts +10 -1
- package/dist/server-fns/collections/create.js +5 -2
- package/dist/server-fns/collections/delete-locale.d.ts +10 -1
- package/dist/server-fns/collections/delete-locale.js +5 -2
- package/dist/server-fns/collections/delete.d.ts +10 -1
- package/dist/server-fns/collections/delete.js +5 -2
- package/dist/server-fns/collections/duplicate.d.ts +10 -1
- package/dist/server-fns/collections/duplicate.js +5 -2
- package/dist/server-fns/collections/get.d.ts +7 -0
- package/dist/server-fns/collections/get.js +8 -3
- package/dist/server-fns/collections/history.d.ts +3 -1
- package/dist/server-fns/collections/history.js +4 -1
- package/dist/server-fns/collections/index.d.ts +21 -21
- package/dist/server-fns/collections/list.d.ts +10 -1
- package/dist/server-fns/collections/list.js +5 -2
- package/dist/server-fns/collections/reindex.d.ts +3 -1
- package/dist/server-fns/collections/reindex.js +4 -1
- package/dist/server-fns/collections/reorder.d.ts +10 -1
- package/dist/server-fns/collections/reorder.js +5 -2
- package/dist/server-fns/collections/restore-version.d.ts +10 -1
- package/dist/server-fns/collections/restore-version.js +5 -2
- package/dist/server-fns/collections/scheduled-publication.d.ts +22 -5
- package/dist/server-fns/collections/scheduled-publication.js +17 -6
- package/dist/server-fns/collections/set-list-view-preference.d.ts +3 -1
- package/dist/server-fns/collections/set-list-view-preference.js +4 -1
- package/dist/server-fns/collections/stats.js +4 -1
- package/dist/server-fns/collections/status.d.ts +13 -2
- package/dist/server-fns/collections/status.js +8 -3
- package/dist/server-fns/collections/tree.d.ts +22 -5
- package/dist/server-fns/collections/tree.js +17 -6
- package/dist/server-fns/collections/update.d.ts +13 -2
- package/dist/server-fns/collections/update.js +8 -3
- package/dist/server-fns/collections/upload.d.ts +10 -1
- package/dist/server-fns/collections/upload.js +4 -1
- package/dist/server-fns/i18n/set-locale.d.ts +3 -1
- package/dist/server-fns/i18n/set-locale.js +18 -3
- package/dist/server-fns/preview/disable.d.ts +3 -1
- package/dist/server-fns/preview/disable.js +4 -1
- package/dist/server-fns/preview/enable.d.ts +3 -1
- package/dist/server-fns/preview/enable.js +4 -1
- package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +10 -1
- package/dist/server-fns/singletons/cancel-scheduled-publish.js +5 -2
- package/dist/server-fns/singletons/change-status.d.ts +10 -1
- package/dist/server-fns/singletons/change-status.js +5 -2
- package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +10 -1
- package/dist/server-fns/singletons/confirm-scheduled-publish.js +5 -2
- package/dist/server-fns/singletons/copy-to-locale.d.ts +10 -1
- package/dist/server-fns/singletons/copy-to-locale.js +5 -2
- package/dist/server-fns/singletons/find-by-version.d.ts +3 -1
- package/dist/server-fns/singletons/find-by-version.js +4 -1
- package/dist/server-fns/singletons/get-scheduled-publish.d.ts +3 -1
- package/dist/server-fns/singletons/get-scheduled-publish.js +4 -1
- package/dist/server-fns/singletons/get.d.ts +3 -1
- package/dist/server-fns/singletons/get.js +4 -1
- package/dist/server-fns/singletons/history.d.ts +3 -1
- package/dist/server-fns/singletons/history.js +4 -1
- package/dist/server-fns/singletons/restore-version.d.ts +10 -1
- package/dist/server-fns/singletons/restore-version.js +5 -2
- package/dist/server-fns/singletons/schedule-publish.d.ts +10 -1
- package/dist/server-fns/singletons/schedule-publish.js +5 -2
- package/dist/server-fns/singletons/unpublish.d.ts +10 -1
- package/dist/server-fns/singletons/unpublish.js +5 -2
- package/dist/server-fns/singletons/update.d.ts +10 -1
- package/dist/server-fns/singletons/update.js +5 -2
- package/package.json +19 -19
- package/src/admin-shell/chrome/admin-app-bar.tsx +11 -6
- package/src/admin-shell/chrome/session-change-boundary.test.tsx +78 -0
- package/src/admin-shell/chrome/session-change-boundary.tsx +94 -0
- package/src/admin-shell/chrome/sign-in-page.tsx +68 -3
- package/src/integrations/byline-admin-services.ts +7 -1
- package/src/integrations/client-ip.test.node.ts +45 -0
- package/src/integrations/client-ip.ts +54 -0
- package/src/integrations/framework-fetch-shape.test.node.ts +56 -0
- package/src/integrations/session-client.test.node.ts +121 -0
- package/src/integrations/session-coordination.test.node.ts +77 -0
- package/src/integrations/session-coordination.ts +96 -0
- package/src/integrations/session-middleware.test.node.ts +87 -0
- package/src/integrations/session-middleware.ts +107 -0
- package/src/integrations/session-request-kind.ts +18 -0
- package/src/integrations/session-request-middleware.ts +17 -0
- package/src/integrations/sign-in-body.test.node.ts +59 -0
- package/src/integrations/sign-in-body.ts +50 -0
- package/src/integrations/sign-in-middleware.ts +24 -0
- package/src/routes/create-admin-layout-route.tsx +48 -30
- package/src/routes/create-sign-in-route.tsx +10 -2
- package/src/server-fns/admin-account/change-password.test.node.ts +69 -0
- package/src/server-fns/admin-account/change-password.ts +13 -2
- package/src/server-fns/admin-account/get.ts +2 -0
- package/src/server-fns/admin-account/update.ts +2 -0
- package/src/server-fns/admin-activity/get.ts +2 -0
- package/src/server-fns/admin-permissions/get-role-abilities.ts +2 -0
- package/src/server-fns/admin-permissions/list-registered.ts +5 -4
- package/src/server-fns/admin-permissions/set-role-abilities.ts +2 -0
- package/src/server-fns/admin-permissions/who-has.ts +2 -0
- package/src/server-fns/admin-roles/create.ts +2 -0
- package/src/server-fns/admin-roles/delete.ts +2 -0
- package/src/server-fns/admin-roles/get.ts +2 -0
- package/src/server-fns/admin-roles/list.ts +5 -4
- package/src/server-fns/admin-roles/reorder.ts +2 -0
- package/src/server-fns/admin-roles/update.ts +2 -0
- package/src/server-fns/admin-users/create.ts +2 -0
- package/src/server-fns/admin-users/delete.ts +2 -0
- package/src/server-fns/admin-users/disable.ts +2 -0
- package/src/server-fns/admin-users/enable.ts +2 -0
- package/src/server-fns/admin-users/get-user-roles.ts +2 -0
- package/src/server-fns/admin-users/get.ts +2 -0
- package/src/server-fns/admin-users/list.ts +2 -0
- package/src/server-fns/admin-users/set-password.ts +2 -0
- package/src/server-fns/admin-users/set-user-roles.ts +2 -0
- package/src/server-fns/admin-users/update.ts +2 -0
- package/src/server-fns/ai/execute.ts +3 -0
- package/src/server-fns/analytics/maintenance.ts +4 -0
- package/src/server-fns/analytics/queries.ts +14 -8
- package/src/server-fns/auth/current-user.ts +7 -3
- package/src/server-fns/auth/renew.test.node.ts +85 -0
- package/src/server-fns/auth/renew.ts +54 -0
- package/src/server-fns/auth/sign-in.test.node.ts +114 -0
- package/src/server-fns/auth/sign-in.ts +80 -27
- package/src/server-fns/auth/sign-out.test.node.ts +85 -0
- package/src/server-fns/auth/sign-out.ts +44 -19
- package/src/server-fns/collections/audit.ts +2 -0
- package/src/server-fns/collections/copy-to-locale.ts +3 -1
- package/src/server-fns/collections/create.ts +3 -1
- package/src/server-fns/collections/delete-locale.ts +3 -1
- package/src/server-fns/collections/delete.ts +3 -1
- package/src/server-fns/collections/duplicate.ts +3 -1
- package/src/server-fns/collections/get.ts +4 -1
- package/src/server-fns/collections/history.ts +2 -0
- package/src/server-fns/collections/list.ts +3 -1
- package/src/server-fns/collections/reindex.ts +2 -0
- package/src/server-fns/collections/reorder.ts +3 -1
- package/src/server-fns/collections/restore-version.ts +3 -1
- package/src/server-fns/collections/revision-input.test.node.ts +14 -0
- package/src/server-fns/collections/scheduled-publication.ts +14 -7
- package/src/server-fns/collections/set-list-view-preference.ts +2 -0
- package/src/server-fns/collections/stats.ts +2 -0
- package/src/server-fns/collections/status.ts +4 -1
- package/src/server-fns/collections/tree.ts +7 -1
- package/src/server-fns/collections/update.ts +4 -1
- package/src/server-fns/collections/upload-singleton.test.node.ts +8 -1
- package/src/server-fns/collections/upload.ts +10 -0
- package/src/server-fns/i18n/set-locale.ts +19 -3
- package/src/server-fns/preview/disable.ts +8 -4
- package/src/server-fns/preview/enable.ts +9 -5
- package/src/server-fns/singletons/cancel-scheduled-publish.ts +3 -1
- package/src/server-fns/singletons/change-status.ts +3 -1
- package/src/server-fns/singletons/confirm-scheduled-publish.ts +3 -1
- package/src/server-fns/singletons/copy-to-locale.ts +3 -1
- package/src/server-fns/singletons/find-by-version.ts +2 -0
- package/src/server-fns/singletons/get-scheduled-publish.ts +2 -0
- package/src/server-fns/singletons/get.ts +2 -0
- package/src/server-fns/singletons/history.ts +2 -0
- package/src/server-fns/singletons/restore-version.ts +3 -1
- package/src/server-fns/singletons/schedule-publish.ts +3 -1
- package/src/server-fns/singletons/singletons.test.node.ts +6 -0
- package/src/server-fns/singletons/unpublish.ts +3 -1
- package/src/server-fns/singletons/update.ts +3 -1
|
@@ -0,0 +1,114 @@
|
|
|
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 { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
10
|
+
|
|
11
|
+
const mocks = vi.hoisted(() => ({
|
|
12
|
+
config: {} as any,
|
|
13
|
+
request: null as unknown as Request,
|
|
14
|
+
signIn: vi.fn(),
|
|
15
|
+
limiter: vi.fn(),
|
|
16
|
+
acquire: vi.fn(),
|
|
17
|
+
release: vi.fn(),
|
|
18
|
+
outcome: vi.fn(),
|
|
19
|
+
setCookies: vi.fn(),
|
|
20
|
+
}))
|
|
21
|
+
vi.mock('@tanstack/react-start', () => ({
|
|
22
|
+
createServerFn: () => ({
|
|
23
|
+
validator: (validate: (input: unknown) => unknown) => ({
|
|
24
|
+
handler: (handler: any) => ({ validate, handler }),
|
|
25
|
+
}),
|
|
26
|
+
}),
|
|
27
|
+
}))
|
|
28
|
+
vi.mock('@tanstack/react-start/server', () => ({
|
|
29
|
+
getRequest: () => mocks.request,
|
|
30
|
+
getRequestHeader: () => undefined,
|
|
31
|
+
}))
|
|
32
|
+
vi.mock('@byline/core', () => ({ getServerConfig: () => mocks.config }))
|
|
33
|
+
vi.mock('@byline/client/server', () => ({
|
|
34
|
+
setSessionCookies: mocks.setCookies,
|
|
35
|
+
readAccessTokenCookie: () => undefined,
|
|
36
|
+
readRefreshTokenCookie: () => undefined,
|
|
37
|
+
}))
|
|
38
|
+
vi.mock('../../i18n/locale-cookie.js', () => ({ readAdminLocaleCookie: () => null }))
|
|
39
|
+
vi.mock('../../integrations/byline-core.js', () => ({
|
|
40
|
+
bylineCore: () => {
|
|
41
|
+
throw new Error('unused')
|
|
42
|
+
},
|
|
43
|
+
}))
|
|
44
|
+
|
|
45
|
+
import { checkSignInBody } from '../../integrations/sign-in-body.js'
|
|
46
|
+
import { adminSignIn } from './sign-in.js'
|
|
47
|
+
|
|
48
|
+
const fn = adminSignIn as unknown as {
|
|
49
|
+
validate: (input: unknown) => unknown
|
|
50
|
+
handler: (args: any) => Promise<unknown>
|
|
51
|
+
}
|
|
52
|
+
const input = { email: 'admin@example.test', password: 'legacy' }
|
|
53
|
+
beforeEach(async () => {
|
|
54
|
+
vi.clearAllMocks()
|
|
55
|
+
mocks.request = new Request('http://localhost/sign-in', {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
headers: { 'content-type': 'application/json' },
|
|
58
|
+
body: '{}',
|
|
59
|
+
})
|
|
60
|
+
await checkSignInBody(mocks.request)
|
|
61
|
+
mocks.acquire.mockResolvedValue(mocks.release)
|
|
62
|
+
mocks.limiter.mockResolvedValue({ allowed: true, retryAfterSeconds: 0 })
|
|
63
|
+
mocks.signIn.mockResolvedValue({ actor: { id: 'test' } })
|
|
64
|
+
mocks.config = {
|
|
65
|
+
sessionProvider: { signInWithPassword: mocks.signIn },
|
|
66
|
+
passwordSignIn: {
|
|
67
|
+
resolveClientIp: () => '192.0.2.1',
|
|
68
|
+
limiter: { consume: mocks.limiter, acquire: mocks.acquire, recordResult: mocks.outcome },
|
|
69
|
+
},
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
describe('password sign-in protection', () => {
|
|
73
|
+
it('admits validated legacy credentials and sets cookies after authentication', async () => {
|
|
74
|
+
expect(await fn.handler({ data: fn.validate(input) })).toEqual({ userId: 'test' })
|
|
75
|
+
expect(mocks.limiter).toHaveBeenCalledWith({ email: input.email, ip: '192.0.2.1' })
|
|
76
|
+
expect(mocks.signIn).toHaveBeenCalledWith({ ...input, ip: '192.0.2.1', userAgent: undefined })
|
|
77
|
+
expect(mocks.setCookies).toHaveBeenCalledTimes(1)
|
|
78
|
+
})
|
|
79
|
+
it('rejects oversized credentials during validation', () => {
|
|
80
|
+
expect(() => fn.validate({ ...input, password: 'a'.repeat(129) })).toThrow()
|
|
81
|
+
expect(mocks.signIn).not.toHaveBeenCalled()
|
|
82
|
+
})
|
|
83
|
+
it('returns 429 with Retry-After before the provider is called', async () => {
|
|
84
|
+
mocks.limiter.mockResolvedValue({ allowed: false, retryAfterSeconds: 42 })
|
|
85
|
+
const error = await fn.handler({ data: input }).catch((e) => e)
|
|
86
|
+
expect(error.status).toBe(429)
|
|
87
|
+
expect(error.headers.get('Retry-After')).toBe('42')
|
|
88
|
+
expect(mocks.signIn).not.toHaveBeenCalled()
|
|
89
|
+
expect(mocks.setCookies).not.toHaveBeenCalled()
|
|
90
|
+
})
|
|
91
|
+
it('sheds capacity before accessing the store or provider', async () => {
|
|
92
|
+
mocks.acquire.mockResolvedValue(null)
|
|
93
|
+
expect((await fn.handler({ data: input }).catch((e) => e)).status).toBe(429)
|
|
94
|
+
expect(mocks.limiter).not.toHaveBeenCalled()
|
|
95
|
+
expect(mocks.signIn).not.toHaveBeenCalled()
|
|
96
|
+
})
|
|
97
|
+
it('releases capacity and records failed verification', async () => {
|
|
98
|
+
mocks.signIn.mockRejectedValueOnce(new Error('invalid credentials'))
|
|
99
|
+
await expect(fn.handler({ data: input })).rejects.toThrow('invalid credentials')
|
|
100
|
+
expect(mocks.release).toHaveBeenCalledTimes(1)
|
|
101
|
+
expect(mocks.outcome).toHaveBeenCalledWith({ email: input.email, ip: '192.0.2.1' }, 'failure')
|
|
102
|
+
})
|
|
103
|
+
it.each(['store', 'ip', 'middleware', 'config'])(
|
|
104
|
+
'fails closed when %s protection is unavailable',
|
|
105
|
+
async (kind) => {
|
|
106
|
+
if (kind === 'store') mocks.limiter.mockRejectedValue(new Error('DB unavailable'))
|
|
107
|
+
if (kind === 'ip') mocks.config.passwordSignIn.resolveClientIp = () => null
|
|
108
|
+
if (kind === 'middleware') mocks.request = new Request('http://localhost/sign-in')
|
|
109
|
+
if (kind === 'config') delete mocks.config.passwordSignIn
|
|
110
|
+
expect((await fn.handler({ data: input }).catch((e) => e)).status).toBe(503)
|
|
111
|
+
expect(mocks.signIn).not.toHaveBeenCalled()
|
|
112
|
+
}
|
|
113
|
+
)
|
|
114
|
+
})
|
|
@@ -18,13 +18,21 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
import { createServerFn } from '@tanstack/react-start'
|
|
21
|
-
import { getRequestHeader } from '@tanstack/react-start/server'
|
|
22
|
-
|
|
23
|
-
import {
|
|
21
|
+
import { getRequest, getRequestHeader } from '@tanstack/react-start/server'
|
|
22
|
+
|
|
23
|
+
import type { SignInAdmission } from '@byline/auth'
|
|
24
|
+
import {
|
|
25
|
+
readAccessTokenCookie,
|
|
26
|
+
readRefreshTokenCookie,
|
|
27
|
+
setSessionCookies,
|
|
28
|
+
} from '@byline/client/server'
|
|
24
29
|
import { getServerConfig } from '@byline/core'
|
|
30
|
+
import { passwordSignInSchema } from '@byline/core/validation'
|
|
25
31
|
|
|
26
32
|
import { readAdminLocaleCookie } from '../../i18n/locale-cookie.js'
|
|
27
33
|
import { bylineCore } from '../../integrations/byline-core.js'
|
|
34
|
+
import { normalizeClientIp } from '../../integrations/client-ip.js'
|
|
35
|
+
import { wasSignInBodyChecked } from '../../integrations/sign-in-body.js'
|
|
28
36
|
|
|
29
37
|
export interface SignInInput {
|
|
30
38
|
email: string
|
|
@@ -33,17 +41,14 @@ export interface SignInInput {
|
|
|
33
41
|
|
|
34
42
|
export interface SignInResult {
|
|
35
43
|
userId: string
|
|
44
|
+
sessionId: string
|
|
36
45
|
}
|
|
37
46
|
|
|
38
47
|
export const adminSignIn = createServerFn({ method: 'POST' })
|
|
39
|
-
.validator((input:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (typeof input?.password !== 'string' || input.password.length === 0) {
|
|
44
|
-
throw new Error('password is required')
|
|
45
|
-
}
|
|
46
|
-
return { email: input.email, password: input.password }
|
|
48
|
+
.validator((input: unknown) => {
|
|
49
|
+
const parsed = passwordSignInSchema.safeParse(input)
|
|
50
|
+
if (!parsed.success) throw new Response('Invalid sign-in input', { status: 400 })
|
|
51
|
+
return parsed.data
|
|
47
52
|
})
|
|
48
53
|
.handler(async ({ data }): Promise<SignInResult> => {
|
|
49
54
|
const provider = getServerConfig().sessionProvider
|
|
@@ -51,21 +56,69 @@ export const adminSignIn = createServerFn({ method: 'POST' })
|
|
|
51
56
|
throw new Error('no sessionProvider configured')
|
|
52
57
|
}
|
|
53
58
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
}
|
|
59
|
+
const request = getRequest()
|
|
60
|
+
const protection = getServerConfig().passwordSignIn
|
|
61
|
+
if (!wasSignInBodyChecked(request)) {
|
|
62
|
+
throw new Response('Password sign-in request middleware is not configured', { status: 503 })
|
|
63
|
+
}
|
|
64
|
+
if (!protection) {
|
|
65
|
+
throw new Response('Password sign-in protection is not configured', { status: 503 })
|
|
66
|
+
}
|
|
67
|
+
const userAgent = getRequestHeader('user-agent')?.slice(0, 512)
|
|
68
|
+
let ip: string | null
|
|
69
|
+
try {
|
|
70
|
+
ip = normalizeClientIp(await protection.resolveClientIp(request))
|
|
71
|
+
} catch {
|
|
72
|
+
throw new Response('Client address unavailable', { status: 503 })
|
|
73
|
+
}
|
|
74
|
+
if (!ip) throw new Response('Client address unavailable', { status: 503 })
|
|
75
|
+
const tooMany = (seconds: number) =>
|
|
76
|
+
new Response('Too many sign-in attempts', {
|
|
77
|
+
status: 429,
|
|
78
|
+
headers: { 'Retry-After': String(seconds), 'Cache-Control': 'no-store' },
|
|
79
|
+
})
|
|
80
|
+
let release: (() => void) | null
|
|
81
|
+
try {
|
|
82
|
+
release = await protection.limiter.acquire()
|
|
83
|
+
} catch {
|
|
84
|
+
throw new Response('Sign-in temporarily unavailable', { status: 503 })
|
|
85
|
+
}
|
|
86
|
+
if (!release) throw tooMany(1)
|
|
87
|
+
const recordResult = (outcome: 'success' | 'failure') => {
|
|
88
|
+
try {
|
|
89
|
+
protection.limiter.recordResult?.({ email: data.email, ip }, outcome)
|
|
90
|
+
} catch {
|
|
91
|
+
/* Telemetry cannot change authentication outcomes. */
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const result = await (async () => {
|
|
95
|
+
try {
|
|
96
|
+
let admission: SignInAdmission
|
|
97
|
+
try {
|
|
98
|
+
admission = await protection.limiter.consume({ email: data.email, ip })
|
|
99
|
+
} catch {
|
|
100
|
+
throw new Response('Sign-in temporarily unavailable', { status: 503 })
|
|
101
|
+
}
|
|
102
|
+
if (!admission.allowed) throw tooMany(admission.retryAfterSeconds)
|
|
103
|
+
try {
|
|
104
|
+
const result = await provider.signInWithPassword({
|
|
105
|
+
previousAccessToken: readAccessTokenCookie(),
|
|
106
|
+
previousRefreshToken: readRefreshTokenCookie(),
|
|
107
|
+
email: data.email,
|
|
108
|
+
password: data.password,
|
|
109
|
+
userAgent,
|
|
110
|
+
ip,
|
|
111
|
+
})
|
|
112
|
+
recordResult('success')
|
|
113
|
+
return result
|
|
114
|
+
} catch (error) {
|
|
115
|
+
recordResult('failure')
|
|
116
|
+
throw error
|
|
117
|
+
}
|
|
118
|
+
} finally {
|
|
119
|
+
release()
|
|
120
|
+
}
|
|
121
|
+
})()
|
|
69
122
|
|
|
70
123
|
setSessionCookies(result)
|
|
71
124
|
|
|
@@ -82,7 +135,7 @@ export const adminSignIn = createServerFn({ method: 'POST' })
|
|
|
82
135
|
// Swallow — locale sync is not load-bearing for the sign-in flow.
|
|
83
136
|
}
|
|
84
137
|
|
|
85
|
-
return { userId: result.actor.id }
|
|
138
|
+
return { userId: result.actor.id, sessionId: result.sessionId }
|
|
86
139
|
})
|
|
87
140
|
|
|
88
141
|
/**
|
|
@@ -0,0 +1,85 @@
|
|
|
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 { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
10
|
+
|
|
11
|
+
const mocks = vi.hoisted(() => ({
|
|
12
|
+
access: 'access' as string | undefined,
|
|
13
|
+
refresh: 'refresh' as string | undefined,
|
|
14
|
+
revoke: vi.fn(),
|
|
15
|
+
clear: vi.fn(),
|
|
16
|
+
clearPreview: vi.fn(),
|
|
17
|
+
}))
|
|
18
|
+
vi.mock('@tanstack/react-start', () => ({
|
|
19
|
+
createMiddleware: () => ({ client: () => ({}) }),
|
|
20
|
+
createServerFn: () => ({ middleware: () => ({ handler: (handler: any) => handler }) }),
|
|
21
|
+
}))
|
|
22
|
+
vi.mock('@byline/core', () => ({
|
|
23
|
+
getServerConfig: () => ({ sessionProvider: { revokeSession: mocks.revoke } }),
|
|
24
|
+
}))
|
|
25
|
+
vi.mock('@byline/client/server', () => ({
|
|
26
|
+
readAccessTokenCookie: () => mocks.access,
|
|
27
|
+
readRefreshTokenCookie: () => mocks.refresh,
|
|
28
|
+
clearSessionCookies: mocks.clear,
|
|
29
|
+
clearPreviewCookie: mocks.clearPreview,
|
|
30
|
+
}))
|
|
31
|
+
|
|
32
|
+
import { adminSignOut } from './sign-out.js'
|
|
33
|
+
|
|
34
|
+
const call = adminSignOut as unknown as (args: {
|
|
35
|
+
context: { expectedSessionId?: string }
|
|
36
|
+
}) => Promise<unknown>
|
|
37
|
+
beforeEach(() => {
|
|
38
|
+
vi.resetAllMocks()
|
|
39
|
+
mocks.access = 'access'
|
|
40
|
+
mocks.refresh = 'refresh'
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
describe('confirmed logout', () => {
|
|
44
|
+
it('revokes with both observed credentials before clearing cookies', async () => {
|
|
45
|
+
mocks.revoke.mockImplementation(async () => {
|
|
46
|
+
expect(mocks.clear).not.toHaveBeenCalled()
|
|
47
|
+
})
|
|
48
|
+
await expect(call({ context: { expectedSessionId: 'A' } })).resolves.toEqual({ status: 'ok' })
|
|
49
|
+
expect(mocks.revoke).toHaveBeenCalledWith({
|
|
50
|
+
accessToken: 'access',
|
|
51
|
+
refreshToken: 'refresh',
|
|
52
|
+
expectedSessionId: 'A',
|
|
53
|
+
})
|
|
54
|
+
expect(mocks.clear).toHaveBeenCalledOnce()
|
|
55
|
+
})
|
|
56
|
+
it('still requests revocation when only the access cookie remains', async () => {
|
|
57
|
+
mocks.refresh = undefined
|
|
58
|
+
await call({ context: { expectedSessionId: 'A' } })
|
|
59
|
+
expect(mocks.revoke).toHaveBeenCalledWith({
|
|
60
|
+
accessToken: 'access',
|
|
61
|
+
refreshToken: undefined,
|
|
62
|
+
expectedSessionId: 'A',
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
it('does not clear credentials or report success when revocation fails', async () => {
|
|
66
|
+
mocks.revoke.mockRejectedValue(new Error('database unavailable'))
|
|
67
|
+
await expect(call({ context: { expectedSessionId: 'A' } })).rejects.toThrow(
|
|
68
|
+
'database unavailable'
|
|
69
|
+
)
|
|
70
|
+
expect(mocks.clear).not.toHaveBeenCalled()
|
|
71
|
+
expect(mocks.clearPreview).not.toHaveBeenCalled()
|
|
72
|
+
})
|
|
73
|
+
it('requires the page identity before revoking any observed credential', async () => {
|
|
74
|
+
await expect(call({ context: {} })).rejects.toMatchObject({ code: 'ERR_SESSION_CHANGED' })
|
|
75
|
+
expect(mocks.revoke).not.toHaveBeenCalled()
|
|
76
|
+
expect(mocks.clear).not.toHaveBeenCalled()
|
|
77
|
+
})
|
|
78
|
+
it('is idempotent when neither credential is present', async () => {
|
|
79
|
+
mocks.access = undefined
|
|
80
|
+
mocks.refresh = undefined
|
|
81
|
+
await expect(call({ context: {} })).resolves.toEqual({ status: 'ok' })
|
|
82
|
+
expect(mocks.revoke).not.toHaveBeenCalled()
|
|
83
|
+
expect(mocks.clear).toHaveBeenCalledOnce()
|
|
84
|
+
})
|
|
85
|
+
})
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
/**
|
|
10
10
|
* Admin sign-out server function.
|
|
11
11
|
*
|
|
12
|
-
* Revokes the
|
|
12
|
+
* Revokes the login identified by either credential (so a stolen copy cannot be reused),
|
|
13
13
|
* clears both session cookies, and clears the preview-mode cookie.
|
|
14
|
-
* Idempotent — if the caller already lacks
|
|
14
|
+
* Idempotent — if the caller already lacks both credential cookies, we still
|
|
15
15
|
* clear whatever's there and return successfully.
|
|
16
16
|
*
|
|
17
17
|
* Note: clearing `byline_preview` here is hygiene, not a security
|
|
@@ -21,32 +21,57 @@
|
|
|
21
21
|
* Clearing it simply means the next sign-in starts in non-preview mode.
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
|
-
import { createServerFn } from '@tanstack/react-start'
|
|
24
|
+
import { createMiddleware, createServerFn } from '@tanstack/react-start'
|
|
25
25
|
|
|
26
|
+
import { ERR_SESSION_CHANGED } from '@byline/auth'
|
|
26
27
|
import {
|
|
27
28
|
clearPreviewCookie,
|
|
28
29
|
clearSessionCookies,
|
|
30
|
+
readAccessTokenCookie,
|
|
29
31
|
readRefreshTokenCookie,
|
|
30
32
|
} from '@byline/client/server'
|
|
31
33
|
import { getServerConfig } from '@byline/core'
|
|
32
34
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
import {
|
|
36
|
+
coordinateAuthAction,
|
|
37
|
+
flagSessionChanged,
|
|
38
|
+
notifySessionAction,
|
|
39
|
+
sessionSnapshot,
|
|
40
|
+
} from '../../integrations/session-coordination.js'
|
|
36
41
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
// certainly be transport-level. Swallow and continue: the cookies
|
|
41
|
-
// get cleared regardless, so the caller's session ends.
|
|
42
|
+
const signOutBoundary = createMiddleware({ type: 'function' }).client(async ({ next }) => {
|
|
43
|
+
const snapshot = sessionSnapshot()
|
|
44
|
+
return coordinateAuthAction(async () => {
|
|
42
45
|
try {
|
|
43
|
-
await
|
|
44
|
-
|
|
45
|
-
|
|
46
|
+
const result = await next({ sendContext: { expectedSessionId: snapshot.sessionId } })
|
|
47
|
+
notifySessionAction()
|
|
48
|
+
return result
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if ((error as { code?: string })?.code === 'ERR_SESSION_CHANGED') flagSessionChanged()
|
|
51
|
+
throw error
|
|
46
52
|
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
clearSessionCookies()
|
|
50
|
-
clearPreviewCookie()
|
|
51
|
-
return { status: 'ok' as const }
|
|
53
|
+
})
|
|
52
54
|
})
|
|
55
|
+
|
|
56
|
+
export const adminSignOut = createServerFn({ method: 'POST' })
|
|
57
|
+
.middleware([signOutBoundary])
|
|
58
|
+
.handler(async ({ context }) => {
|
|
59
|
+
const provider = getServerConfig().sessionProvider
|
|
60
|
+
const refreshToken = readRefreshTokenCookie()
|
|
61
|
+
const accessToken = readAccessTokenCookie()
|
|
62
|
+
|
|
63
|
+
if (refreshToken || accessToken) {
|
|
64
|
+
if (!provider) throw new Error('no sessionProvider configured')
|
|
65
|
+
if (!context.expectedSessionId)
|
|
66
|
+
throw ERR_SESSION_CHANGED({ message: 'logout requires the page login identity' })
|
|
67
|
+
await provider.revokeSession({
|
|
68
|
+
refreshToken,
|
|
69
|
+
accessToken,
|
|
70
|
+
expectedSessionId: context.expectedSessionId,
|
|
71
|
+
})
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
clearSessionCookies()
|
|
75
|
+
clearPreviewCookie()
|
|
76
|
+
return { status: 'ok' as const }
|
|
77
|
+
})
|
|
@@ -12,6 +12,7 @@ import { getAdminBylineClient } from '@byline/client/server'
|
|
|
12
12
|
import { ERR_NOT_FOUND, getLogger } from '@byline/core'
|
|
13
13
|
|
|
14
14
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
15
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
15
16
|
import { type ActorLabelMap, resolveActorLabels } from './actors.js'
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
@@ -49,6 +50,7 @@ export interface AuditLogEntryDto {
|
|
|
49
50
|
// ---------------------------------------------------------------------------
|
|
50
51
|
|
|
51
52
|
export const getCollectionDocumentAuditLog = createServerFn({ method: 'GET' })
|
|
53
|
+
.middleware([adminSessionMiddleware])
|
|
52
54
|
.validator((input: { collection: string; id: string; params?: AuditLogSearchParams }) => input)
|
|
53
55
|
.handler(async ({ data }) => {
|
|
54
56
|
const { collection: path, id, params } = data
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
2
1
|
/**
|
|
3
2
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
4
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -15,6 +14,8 @@ import type { CopyToLocaleResult, DocumentLifecycleContext } from '@byline/core/
|
|
|
15
14
|
import { copyToLocale } from '@byline/core/services'
|
|
16
15
|
|
|
17
16
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
17
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
18
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
18
19
|
import {
|
|
19
20
|
type CollectionDocumentCommittedHookFailureResponse,
|
|
20
21
|
toCommittedDocumentHookFailureResponse,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
* failures propagate to TanStack Start's transport layer.
|
|
35
36
|
*/
|
|
36
37
|
export const copyDocumentToLocale = createServerFn({ method: 'POST' })
|
|
38
|
+
.middleware([adminSessionMiddleware])
|
|
37
39
|
.validator(
|
|
38
40
|
(input: {
|
|
39
41
|
expectedRevision: number
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
2
1
|
/**
|
|
3
2
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
4
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -15,6 +14,8 @@ import type { CreateDocumentResult, DocumentLifecycleContext } from '@byline/cor
|
|
|
15
14
|
import { createDocument } from '@byline/core/services'
|
|
16
15
|
|
|
17
16
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
17
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
18
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
18
19
|
import { toCommittedDocumentHookFailureResponse } from './save-outcome.js'
|
|
19
20
|
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -22,6 +23,7 @@ import { toCommittedDocumentHookFailureResponse } from './save-outcome.js'
|
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
23
24
|
|
|
24
25
|
export const createCollectionDocument = createServerFn({ method: 'POST' })
|
|
26
|
+
.middleware([adminSessionMiddleware])
|
|
25
27
|
.validator(
|
|
26
28
|
(input: {
|
|
27
29
|
collection: string
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
2
1
|
/**
|
|
3
2
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
4
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -15,6 +14,8 @@ import type { DeleteLocaleResult, DocumentLifecycleContext } from '@byline/core/
|
|
|
15
14
|
import { deleteLocale } from '@byline/core/services'
|
|
16
15
|
|
|
17
16
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
17
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
18
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
18
19
|
import {
|
|
19
20
|
type CollectionDocumentCommittedHookFailureResponse,
|
|
20
21
|
toCommittedDocumentHookFailureResponse,
|
|
@@ -35,6 +36,7 @@ import {
|
|
|
35
36
|
* transport layer.
|
|
36
37
|
*/
|
|
37
38
|
export const deleteDocumentLocale = createServerFn({ method: 'POST' })
|
|
39
|
+
.middleware([adminSessionMiddleware])
|
|
38
40
|
.validator(
|
|
39
41
|
(input: { expectedRevision: number; collection: string; id: string; locale: string }) => input
|
|
40
42
|
)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
2
1
|
/**
|
|
3
2
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
4
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -15,6 +14,8 @@ import type { DocumentLifecycleContext } from '@byline/core/services'
|
|
|
15
14
|
import { deleteDocument as deleteDocumentService } from '@byline/core/services'
|
|
16
15
|
|
|
17
16
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
17
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
18
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
18
19
|
import { toDeleteDocumentResponse } from './delete-outcome.js'
|
|
19
20
|
|
|
20
21
|
// ---------------------------------------------------------------------------
|
|
@@ -22,6 +23,7 @@ import { toDeleteDocumentResponse } from './delete-outcome.js'
|
|
|
22
23
|
// ---------------------------------------------------------------------------
|
|
23
24
|
|
|
24
25
|
export const deleteDocument = createServerFn({ method: 'POST' })
|
|
26
|
+
.middleware([adminSessionMiddleware])
|
|
25
27
|
.validator((input: { expectedRevision: number; collection: string; id: string }) => input)
|
|
26
28
|
.handler(
|
|
27
29
|
withDocumentMutationErrors(async ({ data: input }) => {
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
2
1
|
/**
|
|
3
2
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
4
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -15,6 +14,8 @@ import type { DocumentLifecycleContext, DuplicateDocumentResult } from '@byline/
|
|
|
15
14
|
import { duplicateDocument } from '@byline/core/services'
|
|
16
15
|
|
|
17
16
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
17
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
18
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
18
19
|
import {
|
|
19
20
|
type CollectionDocumentCommittedHookFailureResponse,
|
|
20
21
|
toCommittedDocumentHookFailureResponse,
|
|
@@ -36,6 +37,7 @@ import {
|
|
|
36
37
|
* TanStack Start's transport layer for the client to branch on.
|
|
37
38
|
*/
|
|
38
39
|
export const duplicateCollectionDocument = createServerFn({ method: 'POST' })
|
|
40
|
+
.middleware([adminSessionMiddleware])
|
|
39
41
|
.validator((input: { expectedRevision: number; collection: string; id: string }) => input)
|
|
40
42
|
.handler(
|
|
41
43
|
withDocumentMutationErrors(
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withEditableRevision } from '../editable-revision.js'
|
|
2
1
|
/**
|
|
3
2
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
4
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -21,7 +20,9 @@ import {
|
|
|
21
20
|
} from '@byline/core'
|
|
22
21
|
|
|
23
22
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
23
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
24
24
|
import { resolveAdminDocumentRead } from '../admin-document-presentation.js'
|
|
25
|
+
import { withEditableRevision } from '../editable-revision.js'
|
|
25
26
|
import { serialise } from './utils'
|
|
26
27
|
|
|
27
28
|
// ---------------------------------------------------------------------------
|
|
@@ -29,6 +30,7 @@ import { serialise } from './utils'
|
|
|
29
30
|
// ---------------------------------------------------------------------------
|
|
30
31
|
|
|
31
32
|
const getDocumentFn = createServerFn({ method: 'GET' })
|
|
33
|
+
.middleware([adminSessionMiddleware])
|
|
32
34
|
.validator(
|
|
33
35
|
(input: {
|
|
34
36
|
collection: string
|
|
@@ -177,6 +179,7 @@ const getDocumentFn = createServerFn({ method: 'GET' })
|
|
|
177
179
|
// ---------------------------------------------------------------------------
|
|
178
180
|
|
|
179
181
|
const getDocumentByVersionFn = createServerFn({ method: 'GET' })
|
|
182
|
+
.middleware([adminSessionMiddleware])
|
|
180
183
|
.validator((input: { collection: string; versionId: string; locale?: string }) => input)
|
|
181
184
|
.handler(async ({ data }) => {
|
|
182
185
|
const { collection: path, versionId, locale } = data
|
|
@@ -12,6 +12,7 @@ import { getAdminBylineClient } from '@byline/client/server'
|
|
|
12
12
|
import { ERR_NOT_FOUND, getCollectionSchemasForPath, getLogger } from '@byline/core'
|
|
13
13
|
|
|
14
14
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
15
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
15
16
|
import { type ActorLabelMap, resolveActorLabels } from './actors.js'
|
|
16
17
|
import { serialise } from './utils'
|
|
17
18
|
|
|
@@ -32,6 +33,7 @@ export interface HistorySearchParams {
|
|
|
32
33
|
// ---------------------------------------------------------------------------
|
|
33
34
|
|
|
34
35
|
export const getCollectionDocumentHistory = createServerFn({ method: 'GET' })
|
|
36
|
+
.middleware([adminSessionMiddleware])
|
|
35
37
|
.validator((input: { collection: string; id: string; params: HistorySearchParams }) => input)
|
|
36
38
|
.handler(async ({ data }) => {
|
|
37
39
|
const { collection: path, id, params } = data
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withEditableRevision } from '../editable-revision.js'
|
|
2
1
|
/**
|
|
3
2
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
4
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -25,6 +24,8 @@ import {
|
|
|
25
24
|
|
|
26
25
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
27
26
|
import { bylineCore } from '../../integrations/byline-core.js'
|
|
27
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
28
|
+
import { withEditableRevision } from '../editable-revision.js'
|
|
28
29
|
import { resolveListViewState, sortableFieldNames } from './list-view-state.js'
|
|
29
30
|
import { serialise } from './utils'
|
|
30
31
|
import type { ListViewPreferenceValue } from './list-view-state.js'
|
|
@@ -49,6 +50,7 @@ export interface CollectionSearchParams {
|
|
|
49
50
|
// ---------------------------------------------------------------------------
|
|
50
51
|
|
|
51
52
|
export const getCollectionDocuments = createServerFn({ method: 'GET' })
|
|
53
|
+
.middleware([adminSessionMiddleware])
|
|
52
54
|
.validator((input: { collection: string; params: CollectionSearchParams }) => input)
|
|
53
55
|
.handler(async ({ data }) => {
|
|
54
56
|
const { collection: path, params } = data
|
|
@@ -26,10 +26,12 @@ import { getAdminBylineClient } from '@byline/client/server'
|
|
|
26
26
|
import { ERR_NOT_FOUND, getLogger } from '@byline/core'
|
|
27
27
|
|
|
28
28
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
29
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
29
30
|
|
|
30
31
|
export type { ReindexResult }
|
|
31
32
|
|
|
32
33
|
export const reindexCollection = createServerFn({ method: 'POST' })
|
|
34
|
+
.middleware([adminSessionMiddleware])
|
|
33
35
|
.validator((input: { collection: string }) => input)
|
|
34
36
|
.handler(async ({ data }): Promise<ReindexResult> => {
|
|
35
37
|
const { collection } = data
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
2
1
|
/**
|
|
3
2
|
* This Source Code is subject to the terms of the Mozilla Public
|
|
4
3
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
@@ -13,6 +12,8 @@ import { getAdminRequestContext } from '@byline/client/server'
|
|
|
13
12
|
import { ERR_NOT_FOUND, getLogger, getServerConfig, reorderDocument } from '@byline/core'
|
|
14
13
|
|
|
15
14
|
import { ensureCollection } from '../../integrations/api-utils.js'
|
|
15
|
+
import { adminSessionMiddleware } from '../../integrations/session-middleware.js'
|
|
16
|
+
import { withDocumentMutationErrors } from '../document-mutation-errors.js'
|
|
16
17
|
|
|
17
18
|
// ---------------------------------------------------------------------------
|
|
18
19
|
// Reorder a single document within an `orderable: true` collection.
|
|
@@ -30,6 +31,7 @@ import { ensureCollection } from '../../integrations/api-utils.js'
|
|
|
30
31
|
// ---------------------------------------------------------------------------
|
|
31
32
|
|
|
32
33
|
export const reorderCollectionDocument = createServerFn({ method: 'POST' })
|
|
34
|
+
.middleware([adminSessionMiddleware])
|
|
33
35
|
.validator(
|
|
34
36
|
(input: {
|
|
35
37
|
expectedRevision: number
|