@promptbook/cli 0.112.0-100 → 0.112.0-102
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/apps/agents-server/README.md +6 -0
- package/apps/agents-server/package.json +1 -1
- package/apps/agents-server/scripts/prerender-homepage.js +76 -1
- package/apps/agents-server/src/app/actions.ts +0 -6
- package/apps/agents-server/src/app/admin/about/page.tsx +1 -1
- package/apps/agents-server/src/app/admin/login-methods/shibboleth/page.tsx +365 -0
- package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +3 -3
- package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +12 -3
- package/apps/agents-server/src/app/admin/usage/UsageClientTimelineChart.tsx +1 -1
- package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +21 -14
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatPageLayout.tsx +2 -2
- package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatSidebarDefault.tsx +11 -7
- package/apps/agents-server/src/app/api/admin/cli-access/route.ts +27 -123
- package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +33 -125
- package/apps/agents-server/src/app/api/auth/login/route.ts +0 -10
- package/apps/agents-server/src/app/api/auth/shibboleth/acs/route.ts +77 -57
- package/apps/agents-server/src/app/api/auth/shibboleth/login/route.ts +57 -33
- package/apps/agents-server/src/app/api/auth/shibboleth/metadata/route.ts +4 -29
- package/apps/agents-server/src/app/api/auth/shibboleth/status/route.ts +17 -0
- package/apps/agents-server/src/app/api/upload/route.ts +230 -18
- package/apps/agents-server/src/app/api/users/[username]/route.ts +1 -1
- package/apps/agents-server/src/app/api/users/route.ts +5 -5
- package/apps/agents-server/src/app/dashboard/page.tsx +1 -1
- package/apps/agents-server/src/app/docs/[docId]/page.tsx +1 -1
- package/apps/agents-server/src/app/docs/page.tsx +1 -1
- package/apps/agents-server/src/app/globals.css +100 -0
- package/apps/agents-server/src/app/layout.tsx +7 -0
- package/apps/agents-server/src/app/recycle-bin/page.tsx +1 -1
- package/apps/agents-server/src/app/system/settings/KeybindingsSettingsClient.tsx +13 -7
- package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +29 -1
- package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +3 -3
- package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +8 -2
- package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +4 -4
- package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +9 -9
- package/apps/agents-server/src/components/Footer/Footer.tsx +7 -7
- package/apps/agents-server/src/components/Header/Header.tsx +24 -4
- package/apps/agents-server/src/components/Header/HeaderTypes.ts +6 -0
- package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +51 -1
- package/apps/agents-server/src/components/Homepage/Card.tsx +1 -1
- package/apps/agents-server/src/components/Homepage/Section.tsx +3 -1
- package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +12 -1
- package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +100 -149
- package/apps/agents-server/src/components/Skeleton/ConsolePageLoadingSkeleton.tsx +1 -1
- package/apps/agents-server/src/components/Skeleton/DocumentationRouteLoadingSkeleton.tsx +1 -1
- package/apps/agents-server/src/components/Skeleton/HomepageLoadingSkeleton.tsx +1 -1
- package/apps/agents-server/src/components/UsersList/UsersList.tsx +20 -4
- package/apps/agents-server/src/components/UsersList/useUsersAdmin.ts +3 -0
- package/apps/agents-server/src/constants/shibbolethAuth.ts +139 -0
- package/apps/agents-server/src/database/metadataDefaults.ts +54 -80
- package/apps/agents-server/src/database/migrate.ts +30 -1
- package/apps/agents-server/src/database/migrations/2026-06-0100-shibboleth-auth.sql +136 -0
- package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +88 -36
- package/apps/agents-server/src/languages/ServerTranslationKeys.ts +4 -2
- package/apps/agents-server/src/languages/translations/czech.yaml +4 -2
- package/apps/agents-server/src/languages/translations/english.yaml +5 -3
- package/apps/agents-server/src/tools/$provideCdnForServer.ts +69 -23
- package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +54 -6
- package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +4 -6
- package/apps/agents-server/src/utils/cdn/resolveCdnStorageProvider.ts +40 -0
- package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +11 -0
- package/apps/agents-server/src/utils/createAdminTerminalRouteHandlers.ts +264 -0
- package/apps/agents-server/src/utils/shareTargetPayloads.ts +11 -10
- package/apps/agents-server/src/utils/shibbolethAuthentication.ts +729 -621
- package/apps/agents-server/src/utils/upload/createBookEditorUploadHandler.ts +137 -19
- package/esm/index.es.js +1 -1
- package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/esm/src/version.d.ts +1 -1
- package/package.json +2 -2
- package/src/book-components/Chat/MarkdownContent/MarkdownContent.tsx +65 -4
- package/src/other/templates/getTemplatesPipelineCollection.ts +877 -689
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/index.umd.js +1 -1
- package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
- package/umd/src/version.d.ts +1 -1
- package/apps/agents-server/src/app/api/auth/methods/route.ts +0 -44
- package/apps/agents-server/src/constants/authenticationMethods.ts +0 -74
- package/apps/agents-server/src/constants/shibbolethAuthentication.ts +0 -107
|
@@ -24,11 +24,11 @@ export function AgentChatPageLayout({
|
|
|
24
24
|
children,
|
|
25
25
|
}: AgentChatPageLayoutProps) {
|
|
26
26
|
if (isHeadlessMode) {
|
|
27
|
-
return <div className="flex h-full min-h-0 w-full overflow-hidden bg-slate-50/80">{children}</div>;
|
|
27
|
+
return <div className="flex h-full min-h-0 w-full overflow-hidden bg-slate-50/80 dark:bg-slate-950">{children}</div>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
return (
|
|
31
|
-
<div className="flex h-full min-h-0 w-full overflow-hidden bg-slate-50/80">
|
|
31
|
+
<div className="flex h-full min-h-0 w-full overflow-hidden bg-slate-50/80 dark:bg-slate-950">
|
|
32
32
|
{sidebar}
|
|
33
33
|
<section className="flex min-h-0 min-w-0 flex-1 flex-col overflow-hidden">{children}</section>
|
|
34
34
|
</div>
|
|
@@ -183,7 +183,7 @@ function AgentChatSidebarDefaultCollapsedRow({
|
|
|
183
183
|
onClick={() => onChatSelect(item.id)}
|
|
184
184
|
className={`group relative flex w-full min-w-0 flex-col items-center gap-1 rounded-2xl border px-1.5 py-2 transition focus-visible:outline focus-visible:outline-blue-400 focus-visible:outline-offset-2 ${
|
|
185
185
|
item.isActive
|
|
186
|
-
? 'border-blue-300 bg-blue-50 text-blue-700 shadow-sm dark:border-blue-500/40 dark:bg-blue-500/12 dark:text-blue-100'
|
|
186
|
+
? 'agent-chat-sidebar-item-active border-blue-300 bg-blue-50 text-blue-700 shadow-sm dark:border-blue-500/40 dark:bg-blue-500/12 dark:text-blue-100'
|
|
187
187
|
: 'border-transparent bg-slate-100/80 text-slate-700 hover:border-slate-300 hover:bg-slate-100 dark:bg-slate-900/88 dark:text-slate-300 dark:hover:border-slate-700 dark:hover:bg-slate-900'
|
|
188
188
|
} ${item.isEmpty && !item.isActive ? 'opacity-40' : ''}`}
|
|
189
189
|
aria-label={item.content.accessibilityLabel}
|
|
@@ -206,14 +206,16 @@ function AgentChatSidebarDefaultCollapsedRow({
|
|
|
206
206
|
{item.content.messagesCount}
|
|
207
207
|
</span>
|
|
208
208
|
<div
|
|
209
|
-
className={`aspect-square w-full overflow-hidden rounded-xl border px-1.5 py-1.5 text-left ${
|
|
209
|
+
className={`agent-chat-sidebar-item-preview-card aspect-square w-full overflow-hidden rounded-xl border px-1.5 py-1.5 text-left ${
|
|
210
210
|
item.isActive
|
|
211
211
|
? 'border-blue-300 bg-white/90 text-blue-700 dark:border-blue-500/40 dark:bg-slate-950/82 dark:text-blue-100'
|
|
212
212
|
: 'border-slate-200 bg-white/90 text-slate-600 dark:border-slate-700 dark:bg-slate-950/78 dark:text-slate-300'
|
|
213
213
|
}`}
|
|
214
214
|
>
|
|
215
|
-
<div className="max-w-full truncate text-[10px] font-semibold leading-none">
|
|
216
|
-
|
|
215
|
+
<div className="agent-chat-sidebar-item-title max-w-full truncate text-[10px] font-semibold leading-none">
|
|
216
|
+
{item.content.title}
|
|
217
|
+
</div>
|
|
218
|
+
<div className="agent-chat-sidebar-item-preview mt-1 max-w-full truncate text-[9px] leading-tight text-slate-500 dark:text-slate-400">
|
|
217
219
|
{item.content.preview}
|
|
218
220
|
</div>
|
|
219
221
|
</div>
|
|
@@ -241,7 +243,7 @@ function AgentChatSidebarDefaultExpandedRow({
|
|
|
241
243
|
<div
|
|
242
244
|
className={`group relative rounded-xl border ${
|
|
243
245
|
item.isActive
|
|
244
|
-
? 'border-blue-300 bg-blue-50 shadow-sm dark:border-blue-500/40 dark:bg-blue-500/12'
|
|
246
|
+
? 'agent-chat-sidebar-item-active border-blue-300 bg-blue-50 shadow-sm dark:border-blue-500/40 dark:bg-blue-500/12'
|
|
245
247
|
: 'border-transparent hover:border-slate-200 hover:bg-slate-100/80 dark:hover:border-slate-700 dark:hover:bg-slate-900/88'
|
|
246
248
|
} ${item.isEmpty && !item.isActive ? 'opacity-40' : ''}`}
|
|
247
249
|
>
|
|
@@ -256,7 +258,7 @@ function AgentChatSidebarDefaultExpandedRow({
|
|
|
256
258
|
title={item.content.accessibilityLabel}
|
|
257
259
|
>
|
|
258
260
|
<div className="flex items-center gap-2">
|
|
259
|
-
<div className="min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-slate-100">
|
|
261
|
+
<div className="agent-chat-sidebar-item-title min-w-0 flex-1 truncate text-sm font-medium text-slate-800 dark:text-slate-100">
|
|
260
262
|
{item.content.title}
|
|
261
263
|
</div>
|
|
262
264
|
{item.content.sourceChipLabel && (
|
|
@@ -265,7 +267,9 @@ function AgentChatSidebarDefaultExpandedRow({
|
|
|
265
267
|
</span>
|
|
266
268
|
)}
|
|
267
269
|
</div>
|
|
268
|
-
<div className="mt-1 truncate text-xs text-slate-500 dark:text-slate-400">
|
|
270
|
+
<div className="agent-chat-sidebar-item-preview mt-1 truncate text-xs text-slate-500 dark:text-slate-400">
|
|
271
|
+
{item.content.preview}
|
|
272
|
+
</div>
|
|
269
273
|
<div className="mt-2 flex items-center justify-between gap-2">
|
|
270
274
|
<div className={`truncate text-[11px] ${statusClassName}`}>
|
|
271
275
|
{item.content.activityIndicator.compactLabel || item.content.lastActivity}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isUserGlobalAdmin } from '@/src/utils/isUserGlobalAdmin';
|
|
3
|
-
import { createInteractiveTerminalEventStream } from '@/src/utils/createInteractiveTerminalEventStream';
|
|
1
|
+
import { createAdminTerminalRouteHandlers } from '@/src/utils/createAdminTerminalRouteHandlers';
|
|
4
2
|
import {
|
|
5
3
|
getLatestServerCliAccessSession,
|
|
6
4
|
getServerCliAccessSession,
|
|
@@ -14,124 +12,30 @@ export const runtime = 'nodejs';
|
|
|
14
12
|
export const dynamic = 'force-dynamic';
|
|
15
13
|
|
|
16
14
|
/**
|
|
17
|
-
*
|
|
15
|
+
* Shared route handlers for the raw server shell terminal.
|
|
18
16
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return NextResponse.json({
|
|
48
|
-
session: getLatestServerCliAccessSession(),
|
|
49
|
-
});
|
|
50
|
-
} catch (error) {
|
|
51
|
-
return NextResponse.json(
|
|
52
|
-
{ error: error instanceof Error ? error.message : 'Failed to load the CLI access session.' },
|
|
53
|
-
{ status: 500 },
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Starts or reconnects to the raw server shell exposed in the browser.
|
|
60
|
-
*/
|
|
61
|
-
export async function POST() {
|
|
62
|
-
if (!(await isUserGlobalAdmin())) {
|
|
63
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
try {
|
|
67
|
-
return NextResponse.json({
|
|
68
|
-
session: await startServerCliAccessSession(),
|
|
69
|
-
});
|
|
70
|
-
} catch (error) {
|
|
71
|
-
return NextResponse.json(
|
|
72
|
-
{ error: error instanceof Error ? error.message : 'Failed to start the CLI access session.' },
|
|
73
|
-
{ status: 500 },
|
|
74
|
-
);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Sends raw input to the running CLI access shell.
|
|
80
|
-
*/
|
|
81
|
-
export async function PATCH(request: Request) {
|
|
82
|
-
if (!(await isUserGlobalAdmin())) {
|
|
83
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
try {
|
|
87
|
-
const body = (await request.json().catch(() => null)) as
|
|
88
|
-
| {
|
|
89
|
-
readonly sessionId?: string;
|
|
90
|
-
readonly input?: string;
|
|
91
|
-
}
|
|
92
|
-
| null;
|
|
93
|
-
|
|
94
|
-
if (!body?.sessionId || typeof body.input !== 'string') {
|
|
95
|
-
return NextResponse.json({ error: 'CLI access session input is required.' }, { status: 400 });
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
return NextResponse.json({
|
|
99
|
-
session: writeServerCliAccessSessionInput(body.sessionId, body.input),
|
|
100
|
-
});
|
|
101
|
-
} catch (error) {
|
|
102
|
-
return NextResponse.json(
|
|
103
|
-
{ error: error instanceof Error ? error.message : 'Failed to send CLI access input.' },
|
|
104
|
-
{ status: 500 },
|
|
105
|
-
);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Stops one running CLI access shell session.
|
|
111
|
-
*/
|
|
112
|
-
export async function DELETE(request: Request) {
|
|
113
|
-
if (!(await isUserGlobalAdmin())) {
|
|
114
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
try {
|
|
118
|
-
const body = (await request.json().catch(() => null)) as
|
|
119
|
-
| {
|
|
120
|
-
readonly sessionId?: string;
|
|
121
|
-
}
|
|
122
|
-
| null;
|
|
123
|
-
|
|
124
|
-
if (!body?.sessionId) {
|
|
125
|
-
return NextResponse.json({ error: 'CLI access session id is required.' }, { status: 400 });
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return NextResponse.json({
|
|
129
|
-
session: stopServerCliAccessSession(body.sessionId),
|
|
130
|
-
});
|
|
131
|
-
} catch (error) {
|
|
132
|
-
return NextResponse.json(
|
|
133
|
-
{ error: error instanceof Error ? error.message : 'Failed to stop the CLI access session.' },
|
|
134
|
-
{ status: 500 },
|
|
135
|
-
);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
17
|
+
const cliAccessTerminalRouteHandlers = createAdminTerminalRouteHandlers(
|
|
18
|
+
{
|
|
19
|
+
getLatestSession: getLatestServerCliAccessSession,
|
|
20
|
+
getSession: getServerCliAccessSession,
|
|
21
|
+
startSession: startServerCliAccessSession,
|
|
22
|
+
writeSessionInput: writeServerCliAccessSessionInput,
|
|
23
|
+
stopSession: stopServerCliAccessSession,
|
|
24
|
+
subscribeToSession: subscribeToServerCliAccessSession,
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
loadErrorMessage: 'Failed to load the CLI access session.',
|
|
28
|
+
missingStreamSessionIdMessage: 'CLI access session id is required.',
|
|
29
|
+
sessionNotFoundMessage: 'CLI access session was not found.',
|
|
30
|
+
startErrorMessage: 'Failed to start the CLI access session.',
|
|
31
|
+
missingInputMessage: 'CLI access session input is required.',
|
|
32
|
+
sendErrorMessage: 'Failed to send CLI access input.',
|
|
33
|
+
missingStopSessionIdMessage: 'CLI access session id is required.',
|
|
34
|
+
stopErrorMessage: 'Failed to stop the CLI access session.',
|
|
35
|
+
},
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
export const GET = cliAccessTerminalRouteHandlers.GET;
|
|
39
|
+
export const POST = cliAccessTerminalRouteHandlers.POST;
|
|
40
|
+
export const PATCH = cliAccessTerminalRouteHandlers.PATCH;
|
|
41
|
+
export const DELETE = cliAccessTerminalRouteHandlers.DELETE;
|
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
import { NextResponse } from 'next/server';
|
|
2
|
-
import { isUserGlobalAdmin } from '@/src/utils/isUserGlobalAdmin';
|
|
3
|
-
import { createInteractiveTerminalEventStream } from '@/src/utils/createInteractiveTerminalEventStream';
|
|
4
1
|
import {
|
|
5
2
|
getCodeRunnerAuthenticationSession,
|
|
6
3
|
getLatestCodeRunnerAuthenticationSession,
|
|
@@ -9,132 +6,43 @@ import {
|
|
|
9
6
|
subscribeToCodeRunnerAuthenticationSession,
|
|
10
7
|
writeCodeRunnerAuthenticationSessionInput,
|
|
11
8
|
} from '@/src/utils/codeRunnerAuthentication';
|
|
9
|
+
import { createAdminTerminalRouteHandlers } from '@/src/utils/createAdminTerminalRouteHandlers';
|
|
12
10
|
import { readConfiguredCodeRunner } from '@/src/utils/codeRunnerConfiguration';
|
|
13
11
|
|
|
14
12
|
export const runtime = 'nodejs';
|
|
15
13
|
export const dynamic = 'force-dynamic';
|
|
16
14
|
|
|
17
15
|
/**
|
|
18
|
-
*
|
|
16
|
+
* Shared route handlers for the code-runner authentication terminal.
|
|
19
17
|
*/
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
});
|
|
52
|
-
} catch (error) {
|
|
53
|
-
return NextResponse.json(
|
|
54
|
-
{ error: error instanceof Error ? error.message : 'Failed to load the authentication session.' },
|
|
55
|
-
{ status: 500 },
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Starts a new browser-driven authentication terminal for the saved runner.
|
|
62
|
-
*/
|
|
63
|
-
export async function POST() {
|
|
64
|
-
if (!(await isUserGlobalAdmin())) {
|
|
65
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
const { agent } = await readConfiguredCodeRunner();
|
|
70
|
-
return NextResponse.json({
|
|
71
|
-
session: await startCodeRunnerAuthenticationSession(agent),
|
|
72
|
-
});
|
|
73
|
-
} catch (error) {
|
|
74
|
-
return NextResponse.json(
|
|
75
|
-
{ error: error instanceof Error ? error.message : 'Failed to start the authentication session.' },
|
|
76
|
-
{ status: 500 },
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Sends terminal input to a running authentication session.
|
|
83
|
-
*/
|
|
84
|
-
export async function PATCH(request: Request) {
|
|
85
|
-
if (!(await isUserGlobalAdmin())) {
|
|
86
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
try {
|
|
90
|
-
const body = (await request.json().catch(() => null)) as
|
|
91
|
-
| {
|
|
92
|
-
readonly sessionId?: string;
|
|
93
|
-
readonly input?: string;
|
|
94
|
-
}
|
|
95
|
-
| null;
|
|
96
|
-
|
|
97
|
-
if (!body?.sessionId || typeof body.input !== 'string') {
|
|
98
|
-
return NextResponse.json({ error: 'Authentication session input is required.' }, { status: 400 });
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
return NextResponse.json({
|
|
102
|
-
session: writeCodeRunnerAuthenticationSessionInput(body.sessionId, body.input),
|
|
103
|
-
});
|
|
104
|
-
} catch (error) {
|
|
105
|
-
return NextResponse.json(
|
|
106
|
-
{ error: error instanceof Error ? error.message : 'Failed to send authentication input.' },
|
|
107
|
-
{ status: 500 },
|
|
108
|
-
);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/**
|
|
113
|
-
* Stops one authentication terminal from the admin UI.
|
|
114
|
-
*/
|
|
115
|
-
export async function DELETE(request: Request) {
|
|
116
|
-
if (!(await isUserGlobalAdmin())) {
|
|
117
|
-
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const body = (await request.json().catch(() => null)) as
|
|
122
|
-
| {
|
|
123
|
-
readonly sessionId?: string;
|
|
124
|
-
}
|
|
125
|
-
| null;
|
|
126
|
-
|
|
127
|
-
if (!body?.sessionId) {
|
|
128
|
-
return NextResponse.json({ error: 'Authentication session id is required.' }, { status: 400 });
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
return NextResponse.json({
|
|
132
|
-
session: stopCodeRunnerAuthenticationSession(body.sessionId),
|
|
133
|
-
});
|
|
134
|
-
} catch (error) {
|
|
135
|
-
return NextResponse.json(
|
|
136
|
-
{ error: error instanceof Error ? error.message : 'Failed to stop the authentication session.' },
|
|
137
|
-
{ status: 500 },
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
18
|
+
const authenticationTerminalRouteHandlers = createAdminTerminalRouteHandlers(
|
|
19
|
+
{
|
|
20
|
+
async getLatestSession() {
|
|
21
|
+
const { agent } = await readConfiguredCodeRunner();
|
|
22
|
+
return getLatestCodeRunnerAuthenticationSession(agent);
|
|
23
|
+
},
|
|
24
|
+
getSession: getCodeRunnerAuthenticationSession,
|
|
25
|
+
async startSession() {
|
|
26
|
+
const { agent } = await readConfiguredCodeRunner();
|
|
27
|
+
return startCodeRunnerAuthenticationSession(agent);
|
|
28
|
+
},
|
|
29
|
+
writeSessionInput: writeCodeRunnerAuthenticationSessionInput,
|
|
30
|
+
stopSession: stopCodeRunnerAuthenticationSession,
|
|
31
|
+
subscribeToSession: subscribeToCodeRunnerAuthenticationSession,
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
loadErrorMessage: 'Failed to load the authentication session.',
|
|
35
|
+
missingStreamSessionIdMessage: 'Authentication session id is required.',
|
|
36
|
+
sessionNotFoundMessage: 'Authentication session was not found.',
|
|
37
|
+
startErrorMessage: 'Failed to start the authentication session.',
|
|
38
|
+
missingInputMessage: 'Authentication session input is required.',
|
|
39
|
+
sendErrorMessage: 'Failed to send authentication input.',
|
|
40
|
+
missingStopSessionIdMessage: 'Authentication session id is required.',
|
|
41
|
+
stopErrorMessage: 'Failed to stop the authentication session.',
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
export const GET = authenticationTerminalRouteHandlers.GET;
|
|
46
|
+
export const POST = authenticationTerminalRouteHandlers.POST;
|
|
47
|
+
export const PATCH = authenticationTerminalRouteHandlers.PATCH;
|
|
48
|
+
export const DELETE = authenticationTerminalRouteHandlers.DELETE;
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { authenticateUser } from '../../../../utils/authenticateUser';
|
|
2
2
|
import { setSession } from '../../../../utils/session';
|
|
3
3
|
import { NextResponse } from 'next/server';
|
|
4
|
-
import {
|
|
5
|
-
AUTHENTICATION_METHODS_METADATA_KEY,
|
|
6
|
-
isAuthenticationMethodEnabled,
|
|
7
|
-
} from '../../../../constants/authenticationMethods';
|
|
8
|
-
import { getMetadata } from '../../../../database/getMetadata';
|
|
9
4
|
|
|
10
5
|
/**
|
|
11
6
|
* Handles post.
|
|
@@ -19,11 +14,6 @@ export async function POST(request: Request) {
|
|
|
19
14
|
return NextResponse.json({ error: 'Username and password are required' }, { status: 400 });
|
|
20
15
|
}
|
|
21
16
|
|
|
22
|
-
if (!isAuthenticationMethodEnabled(await getMetadata(AUTHENTICATION_METHODS_METADATA_KEY), 'PASSWORD')) {
|
|
23
|
-
console.info('Password login rejected because PASSWORD authentication is disabled in metadata.');
|
|
24
|
-
return NextResponse.json({ error: 'Password login is disabled on this server' }, { status: 403 });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
17
|
const user = await authenticateUser(username, password);
|
|
28
18
|
|
|
29
19
|
if (user) {
|
|
@@ -1,92 +1,112 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { revalidatePath } from 'next/cache';
|
|
2
|
+
import { NextResponse } from 'next/server';
|
|
3
3
|
import {
|
|
4
|
-
createShibbolethProfileLogDetails,
|
|
5
4
|
createShibbolethSamlClient,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
findOrCreateShibbolethUser,
|
|
6
|
+
getShibbolethRequestDetails,
|
|
7
|
+
recordShibbolethAuthenticationAttempt,
|
|
8
|
+
resolveShibbolethAuthenticationConfiguration,
|
|
9
|
+
sanitizeShibbolethRelayState,
|
|
11
10
|
} from '../../../../../utils/shibbolethAuthentication';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* Forces this route to run in Node.js because node-saml depends on Node crypto/zlib APIs.
|
|
15
|
-
*/
|
|
16
|
-
export const runtime = 'nodejs';
|
|
11
|
+
import { setSession } from '../../../../../utils/session';
|
|
17
12
|
|
|
18
13
|
/**
|
|
19
14
|
* Handles post.
|
|
20
15
|
*/
|
|
21
|
-
export async function POST(request:
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
if (!configuration.isEnabled) {
|
|
25
|
-
logShibbolethAuthenticationEvent('acs_rejected_disabled');
|
|
26
|
-
return NextResponse.json({ error: 'Shibboleth authentication is not enabled.' }, { status: 404 });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
if (!configuration.isConfigured) {
|
|
30
|
-
const error = new ShibbolethConfigurationError(configuration.missingConfiguration);
|
|
31
|
-
logShibbolethAuthenticationEvent('acs_rejected_incomplete_configuration', {
|
|
32
|
-
missingConfiguration: configuration.missingConfiguration,
|
|
33
|
-
});
|
|
34
|
-
return NextResponse.json({ error: error.message }, { status: 503 });
|
|
35
|
-
}
|
|
16
|
+
export async function POST(request: Request) {
|
|
17
|
+
const requestDetails = getShibbolethRequestDetails(request);
|
|
18
|
+
let relayState = '/';
|
|
36
19
|
|
|
37
20
|
try {
|
|
38
21
|
const formData = await request.formData();
|
|
39
22
|
const samlResponse = formData.get('SAMLResponse');
|
|
40
|
-
|
|
41
|
-
typeof formData.get('RelayState') === 'string' ? String(formData.get('RelayState')) : null,
|
|
42
|
-
);
|
|
23
|
+
relayState = sanitizeShibbolethRelayState(formData.get('RelayState')?.toString());
|
|
43
24
|
|
|
44
|
-
if (typeof samlResponse !== 'string' || samlResponse
|
|
45
|
-
|
|
25
|
+
if (typeof samlResponse !== 'string' || !samlResponse) {
|
|
26
|
+
await recordShibbolethAuthenticationAttempt({
|
|
27
|
+
stage: 'ASSERTION_CONSUMER_SERVICE',
|
|
28
|
+
status: 'FAILED',
|
|
29
|
+
requestDetails,
|
|
30
|
+
relayState,
|
|
31
|
+
errorMessage: 'Missing SAMLResponse.',
|
|
32
|
+
});
|
|
46
33
|
return NextResponse.json({ error: 'Missing SAMLResponse.' }, { status: 400 });
|
|
47
34
|
}
|
|
48
35
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
36
|
+
const configuration = await resolveShibbolethAuthenticationConfiguration({
|
|
37
|
+
requestUrl: request.url,
|
|
38
|
+
isIdentityProviderMetadataValidationEnabled: true,
|
|
52
39
|
});
|
|
53
40
|
|
|
41
|
+
if (!configuration.isActive) {
|
|
42
|
+
await recordShibbolethAuthenticationAttempt({
|
|
43
|
+
stage: 'ASSERTION_CONSUMER_SERVICE',
|
|
44
|
+
status: 'REJECTED',
|
|
45
|
+
requestDetails,
|
|
46
|
+
relayState,
|
|
47
|
+
errorMessage: 'Shibboleth authentication is not active.',
|
|
48
|
+
});
|
|
49
|
+
return NextResponse.json({ error: 'Shibboleth authentication is not active.' }, { status: 404 });
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!configuration.isConfigured) {
|
|
53
|
+
const errorMessage = configuration.errors.join(' ');
|
|
54
|
+
await recordShibbolethAuthenticationAttempt({
|
|
55
|
+
stage: 'ASSERTION_CONSUMER_SERVICE',
|
|
56
|
+
status: 'FAILED',
|
|
57
|
+
requestDetails,
|
|
58
|
+
relayState,
|
|
59
|
+
errorMessage,
|
|
60
|
+
});
|
|
61
|
+
return NextResponse.json({ error: errorMessage }, { status: 503 });
|
|
62
|
+
}
|
|
63
|
+
|
|
54
64
|
const saml = createShibbolethSamlClient(configuration);
|
|
55
|
-
const { profile } = await saml.validatePostResponseAsync({
|
|
56
|
-
SAMLResponse: samlResponse,
|
|
57
|
-
RelayState: relayState,
|
|
58
|
-
});
|
|
65
|
+
const { profile } = await saml.validatePostResponseAsync({ SAMLResponse: samlResponse });
|
|
59
66
|
|
|
60
67
|
if (!profile) {
|
|
61
|
-
|
|
62
|
-
|
|
68
|
+
await recordShibbolethAuthenticationAttempt({
|
|
69
|
+
stage: 'ASSERTION_CONSUMER_SERVICE',
|
|
70
|
+
status: 'FAILED',
|
|
71
|
+
requestDetails,
|
|
72
|
+
relayState,
|
|
73
|
+
errorMessage: 'Shibboleth response did not include a user profile.',
|
|
74
|
+
});
|
|
75
|
+
return NextResponse.json({ error: 'Shibboleth response did not include a user profile.' }, { status: 401 });
|
|
63
76
|
}
|
|
64
77
|
|
|
65
|
-
|
|
66
|
-
'acs_profile_validated',
|
|
67
|
-
createShibbolethProfileLogDetails(profile, configuration.usernameAttribute),
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const user = await resolveShibbolethUser(profile, configuration);
|
|
78
|
+
const linkedUser = await findOrCreateShibbolethUser(profile);
|
|
71
79
|
await setSession({
|
|
72
|
-
username: user.username,
|
|
73
|
-
isAdmin: user.isAdmin,
|
|
80
|
+
username: linkedUser.user.username,
|
|
81
|
+
isAdmin: linkedUser.user.isAdmin,
|
|
74
82
|
isGlobalAdmin: false,
|
|
75
83
|
});
|
|
84
|
+
revalidatePath('/', 'layout');
|
|
76
85
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
86
|
+
await recordShibbolethAuthenticationAttempt({
|
|
87
|
+
stage: 'ASSERTION_CONSUMER_SERVICE',
|
|
88
|
+
status: 'SUCCESS',
|
|
89
|
+
requestDetails,
|
|
81
90
|
relayState,
|
|
91
|
+
userId: linkedUser.user.id,
|
|
92
|
+
email: linkedUser.profileAttributes.email,
|
|
93
|
+
displayName: linkedUser.profileAttributes.displayName,
|
|
94
|
+
nameId: linkedUser.profileAttributes.nameId,
|
|
95
|
+
rawAttributes: linkedUser.profileAttributes.rawAttributes,
|
|
82
96
|
});
|
|
83
97
|
|
|
84
98
|
return NextResponse.redirect(new URL(relayState, request.url), 303);
|
|
85
99
|
} catch (error) {
|
|
86
|
-
|
|
87
|
-
|
|
100
|
+
const errorMessage = error instanceof Error ? error.message : 'Failed to finish Shibboleth authentication.';
|
|
101
|
+
await recordShibbolethAuthenticationAttempt({
|
|
102
|
+
stage: 'ASSERTION_CONSUMER_SERVICE',
|
|
103
|
+
status: 'FAILED',
|
|
104
|
+
requestDetails,
|
|
105
|
+
relayState,
|
|
106
|
+
errorMessage,
|
|
88
107
|
});
|
|
89
108
|
|
|
90
|
-
|
|
109
|
+
console.error('Shibboleth assertion consumer service error:', error);
|
|
110
|
+
return NextResponse.json({ error: errorMessage }, { status: 401 });
|
|
91
111
|
}
|
|
92
112
|
}
|