@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.
Files changed (78) hide show
  1. package/apps/agents-server/README.md +6 -0
  2. package/apps/agents-server/package.json +1 -1
  3. package/apps/agents-server/scripts/prerender-homepage.js +76 -1
  4. package/apps/agents-server/src/app/actions.ts +0 -6
  5. package/apps/agents-server/src/app/admin/about/page.tsx +1 -1
  6. package/apps/agents-server/src/app/admin/login-methods/shibboleth/page.tsx +365 -0
  7. package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +3 -3
  8. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +12 -3
  9. package/apps/agents-server/src/app/admin/usage/UsageClientTimelineChart.tsx +1 -1
  10. package/apps/agents-server/src/app/admin/users/[userId]/UserDetailClient.tsx +21 -14
  11. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatPageLayout.tsx +2 -2
  12. package/apps/agents-server/src/app/agents/[agentName]/chat/AgentChatSidebarDefault.tsx +11 -7
  13. package/apps/agents-server/src/app/api/admin/cli-access/route.ts +27 -123
  14. package/apps/agents-server/src/app/api/admin/code-runners/authentication/route.ts +33 -125
  15. package/apps/agents-server/src/app/api/auth/login/route.ts +0 -10
  16. package/apps/agents-server/src/app/api/auth/shibboleth/acs/route.ts +77 -57
  17. package/apps/agents-server/src/app/api/auth/shibboleth/login/route.ts +57 -33
  18. package/apps/agents-server/src/app/api/auth/shibboleth/metadata/route.ts +4 -29
  19. package/apps/agents-server/src/app/api/auth/shibboleth/status/route.ts +17 -0
  20. package/apps/agents-server/src/app/api/upload/route.ts +230 -18
  21. package/apps/agents-server/src/app/api/users/[username]/route.ts +1 -1
  22. package/apps/agents-server/src/app/api/users/route.ts +5 -5
  23. package/apps/agents-server/src/app/dashboard/page.tsx +1 -1
  24. package/apps/agents-server/src/app/docs/[docId]/page.tsx +1 -1
  25. package/apps/agents-server/src/app/docs/page.tsx +1 -1
  26. package/apps/agents-server/src/app/globals.css +100 -0
  27. package/apps/agents-server/src/app/layout.tsx +7 -0
  28. package/apps/agents-server/src/app/recycle-bin/page.tsx +1 -1
  29. package/apps/agents-server/src/app/system/settings/KeybindingsSettingsClient.tsx +13 -7
  30. package/apps/agents-server/src/components/AdminTerminal/useAdminTerminalSession.ts +29 -1
  31. package/apps/agents-server/src/components/AgentProfile/AgentProfile.tsx +3 -3
  32. package/apps/agents-server/src/components/AgentProfile/AgentProfileImage.tsx +8 -2
  33. package/apps/agents-server/src/components/DocsToolbar/DocsToolbar.tsx +4 -4
  34. package/apps/agents-server/src/components/DocumentationContent/DocumentationContent.tsx +9 -9
  35. package/apps/agents-server/src/components/Footer/Footer.tsx +7 -7
  36. package/apps/agents-server/src/components/Header/Header.tsx +24 -4
  37. package/apps/agents-server/src/components/Header/HeaderTypes.ts +6 -0
  38. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +51 -1
  39. package/apps/agents-server/src/components/Homepage/Card.tsx +1 -1
  40. package/apps/agents-server/src/components/Homepage/Section.tsx +3 -1
  41. package/apps/agents-server/src/components/LayoutWrapper/LayoutWrapper.tsx +12 -1
  42. package/apps/agents-server/src/components/LoginForm/LoginForm.tsx +100 -149
  43. package/apps/agents-server/src/components/Skeleton/ConsolePageLoadingSkeleton.tsx +1 -1
  44. package/apps/agents-server/src/components/Skeleton/DocumentationRouteLoadingSkeleton.tsx +1 -1
  45. package/apps/agents-server/src/components/Skeleton/HomepageLoadingSkeleton.tsx +1 -1
  46. package/apps/agents-server/src/components/UsersList/UsersList.tsx +20 -4
  47. package/apps/agents-server/src/components/UsersList/useUsersAdmin.ts +3 -0
  48. package/apps/agents-server/src/constants/shibbolethAuth.ts +139 -0
  49. package/apps/agents-server/src/database/metadataDefaults.ts +54 -80
  50. package/apps/agents-server/src/database/migrate.ts +30 -1
  51. package/apps/agents-server/src/database/migrations/2026-06-0100-shibboleth-auth.sql +136 -0
  52. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +88 -36
  53. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +4 -2
  54. package/apps/agents-server/src/languages/translations/czech.yaml +4 -2
  55. package/apps/agents-server/src/languages/translations/english.yaml +5 -3
  56. package/apps/agents-server/src/tools/$provideCdnForServer.ts +69 -23
  57. package/apps/agents-server/src/utils/cdn/classes/DigitalOceanSpaces.ts +54 -6
  58. package/apps/agents-server/src/utils/cdn/classes/TrackedFilesStorage.ts +4 -6
  59. package/apps/agents-server/src/utils/cdn/resolveCdnStorageProvider.ts +40 -0
  60. package/apps/agents-server/src/utils/chatExport/renderHtmlToPdfOnServer.ts +11 -0
  61. package/apps/agents-server/src/utils/createAdminTerminalRouteHandlers.ts +264 -0
  62. package/apps/agents-server/src/utils/shareTargetPayloads.ts +11 -10
  63. package/apps/agents-server/src/utils/shibbolethAuthentication.ts +729 -621
  64. package/apps/agents-server/src/utils/upload/createBookEditorUploadHandler.ts +137 -19
  65. package/esm/index.es.js +1 -1
  66. package/esm/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  67. package/esm/src/version.d.ts +1 -1
  68. package/package.json +2 -2
  69. package/src/book-components/Chat/MarkdownContent/MarkdownContent.tsx +65 -4
  70. package/src/other/templates/getTemplatesPipelineCollection.ts +877 -689
  71. package/src/version.ts +2 -2
  72. package/src/versions.txt +2 -0
  73. package/umd/index.umd.js +1 -1
  74. package/umd/src/book-components/Chat/MarkdownContent/MarkdownContent.d.ts +1 -0
  75. package/umd/src/version.d.ts +1 -1
  76. package/apps/agents-server/src/app/api/auth/methods/route.ts +0 -44
  77. package/apps/agents-server/src/constants/authenticationMethods.ts +0 -74
  78. package/apps/agents-server/src/constants/shibbolethAuthentication.ts +0 -107
@@ -2,11 +2,6 @@ import { spaceTrim } from 'spacetrim';
2
2
  import { CORE_AGENTS_SERVER } from '../../../../servers';
3
3
  import { DEFAULT_THINKING_MESSAGES } from '../../../../src/utils/DEFAULT_THINKING_MESSAGES';
4
4
  import { ANALYTICS_METADATA_KEYS, getAnalyticsMetadataDefinition } from '../constants/analyticsMetadata';
5
- import {
6
- AUTHENTICATION_METHOD_VALUES,
7
- AUTHENTICATION_METHODS_METADATA_KEY,
8
- DEFAULT_AUTHENTICATION_METHODS_METADATA_VALUE,
9
- } from '../constants/authenticationMethods';
10
5
  import {
11
6
  DEFAULT_FEDERATED_AGENT_IMPORT_RETRY_DELAY_MS,
12
7
  FEDERATED_AGENT_IMPORT_RETRY_DELAY_MS_METADATA_KEY,
@@ -25,24 +20,23 @@ import {
25
20
  import { DEFAULT_THEME_METADATA_KEY, DEFAULT_THEME_MODE, THEME_MODE_OPTIONS } from '../constants/themeMode';
26
21
  import { DEFAULT_NAME_POOL, NAME_POOL_METADATA_KEY, NAME_POOL_OPTIONS } from '../constants/namePool';
27
22
  import { NEW_AGENT_WIZZARD_METADATA_KEY, NEW_AGENT_WIZZARD_OPTIONS } from '../constants/newAgentWizard';
23
+ import {
24
+ DEFAULT_SHIBBOLETH_DISPLAY_NAME_ATTRIBUTE_NAMES,
25
+ DEFAULT_SHIBBOLETH_EMAIL_ATTRIBUTE_NAMES,
26
+ DEFAULT_SHIBBOLETH_UNSTRUCTURED_NAME_ATTRIBUTE_NAMES,
27
+ IS_SHIBBOLETH_AUTH_ACTIVE_METADATA_KEY,
28
+ SHIBBOLETH_DISPLAY_NAME_ATTRIBUTE_NAMES_METADATA_KEY,
29
+ SHIBBOLETH_EMAIL_ATTRIBUTE_NAMES_METADATA_KEY,
30
+ SHIBBOLETH_IDENTITY_PROVIDER_METADATA_URL_METADATA_KEY,
31
+ SHIBBOLETH_IDENTITY_PROVIDER_METADATA_XML_METADATA_KEY,
32
+ SHIBBOLETH_SERVICE_PROVIDER_ENTITY_ID_METADATA_KEY,
33
+ SHIBBOLETH_UNSTRUCTURED_NAME_ATTRIBUTE_NAMES_METADATA_KEY,
34
+ } from '../constants/shibbolethAuth';
28
35
  import {
29
36
  DEFAULT_SERVER_LIMIT_VALUES,
30
37
  MAX_FILE_UPLOAD_SIZE_MB_METADATA_KEY,
31
38
  SERVER_LIMIT_KEYS,
32
39
  } from '../constants/serverLimits';
33
- import {
34
- DEFAULT_SHIBBOLETH_PROVIDER_LABEL,
35
- DEFAULT_SHIBBOLETH_USERNAME_ATTRIBUTE,
36
- SHIBBOLETH_AUTO_CREATE_USERS_METADATA_KEY,
37
- SHIBBOLETH_CALLBACK_URL_METADATA_KEY,
38
- SHIBBOLETH_ENTITY_ID_METADATA_KEY,
39
- SHIBBOLETH_IDP_CERTIFICATE_METADATA_KEY,
40
- SHIBBOLETH_IDP_ENTRYPOINT_METADATA_KEY,
41
- SHIBBOLETH_IDP_ISSUER_METADATA_KEY,
42
- SHIBBOLETH_IDP_METADATA_URL_METADATA_KEY,
43
- SHIBBOLETH_PROVIDER_LABEL_METADATA_KEY,
44
- SHIBBOLETH_USERNAME_ATTRIBUTE_METADATA_KEY,
45
- } from '../constants/shibbolethAuthentication';
46
40
  import { DEFAULT_TOOL_USAGE_LIMITS, TOOL_USAGE_LIMITS_METADATA_KEY } from '../constants/toolUsageLimits';
47
41
  import {
48
42
  IS_SERVER_LANGUAGE_ENFORCED_METADATA_KEY,
@@ -163,6 +157,48 @@ export const metadataDefaults = [
163
157
  type: 'TEXT_SINGLE_LINE',
164
158
  options: SERVER_VISIBILITY_OPTIONS,
165
159
  },
160
+ {
161
+ key: IS_SHIBBOLETH_AUTH_ACTIVE_METADATA_KEY,
162
+ value: 'false',
163
+ note: 'Enable Shibboleth authentication alongside standard username/password login.',
164
+ type: 'BOOLEAN',
165
+ },
166
+ {
167
+ key: SHIBBOLETH_IDENTITY_PROVIDER_METADATA_URL_METADATA_KEY,
168
+ value: '',
169
+ note: 'URL of the Shibboleth Identity Provider metadata XML. For Silesian University this is https://idp-cro.slu.cz/idp/shibboleth.',
170
+ type: 'TEXT_SINGLE_LINE',
171
+ },
172
+ {
173
+ key: SHIBBOLETH_IDENTITY_PROVIDER_METADATA_XML_METADATA_KEY,
174
+ value: '',
175
+ note: 'Optional pasted Shibboleth Identity Provider metadata XML. Used instead of SHIBBOLETH_IDP_METADATA_URL when filled.',
176
+ type: 'TEXT',
177
+ },
178
+ {
179
+ key: SHIBBOLETH_SERVICE_PROVIDER_ENTITY_ID_METADATA_KEY,
180
+ value: '',
181
+ note: 'Optional Service Provider entity ID override. When empty, Agents Server uses the generated Shibboleth metadata endpoint URL.',
182
+ type: 'TEXT_SINGLE_LINE',
183
+ },
184
+ {
185
+ key: SHIBBOLETH_EMAIL_ATTRIBUTE_NAMES_METADATA_KEY,
186
+ value: DEFAULT_SHIBBOLETH_EMAIL_ATTRIBUTE_NAMES,
187
+ note: 'Space-separated SAML attribute names accepted as the user email during Shibboleth login.',
188
+ type: 'TEXT_SINGLE_LINE',
189
+ },
190
+ {
191
+ key: SHIBBOLETH_DISPLAY_NAME_ATTRIBUTE_NAMES_METADATA_KEY,
192
+ value: DEFAULT_SHIBBOLETH_DISPLAY_NAME_ATTRIBUTE_NAMES,
193
+ note: 'Space-separated SAML attribute names accepted as the user display name during Shibboleth login.',
194
+ type: 'TEXT_SINGLE_LINE',
195
+ },
196
+ {
197
+ key: SHIBBOLETH_UNSTRUCTURED_NAME_ATTRIBUTE_NAMES_METADATA_KEY,
198
+ value: DEFAULT_SHIBBOLETH_UNSTRUCTURED_NAME_ATTRIBUTE_NAMES,
199
+ note: 'Space-separated SAML attribute names accepted as the institutional identifier during Shibboleth login.',
200
+ type: 'TEXT_SINGLE_LINE',
201
+ },
166
202
  {
167
203
  key: 'AGENT_NAMING',
168
204
  value: 'Agent / Agents',
@@ -427,68 +463,6 @@ export const metadataDefaults = [
427
463
  note: 'Administrator email address used for password reset and user registration requests.',
428
464
  type: 'TEXT_SINGLE_LINE',
429
465
  },
430
- {
431
- key: AUTHENTICATION_METHODS_METADATA_KEY,
432
- value: DEFAULT_AUTHENTICATION_METHODS_METADATA_VALUE,
433
- note: `Comma-separated login methods enabled on this server. Available values: ${AUTHENTICATION_METHOD_VALUES.join(
434
- ', ',
435
- )}. Shibboleth is inactive unless SHIBBOLETH is included here.`,
436
- type: 'TEXT_SINGLE_LINE',
437
- },
438
- {
439
- key: SHIBBOLETH_IDP_METADATA_URL_METADATA_KEY,
440
- value: '',
441
- note: 'URL of the Shibboleth Identity Provider metadata XML. When set, the IdP SSO URL and signing certificates are loaded from this document.',
442
- type: 'TEXT_SINGLE_LINE',
443
- },
444
- {
445
- key: SHIBBOLETH_IDP_ENTRYPOINT_METADATA_KEY,
446
- value: '',
447
- note: 'Fallback Shibboleth Identity Provider SSO URL used when it cannot be loaded from metadata.',
448
- type: 'TEXT_SINGLE_LINE',
449
- },
450
- {
451
- key: SHIBBOLETH_IDP_CERTIFICATE_METADATA_KEY,
452
- value: '',
453
- note: 'Fallback Shibboleth IdP signing certificate in PEM or base64 form. Multiple certificates can be separated by blank lines.',
454
- type: 'TEXT',
455
- },
456
- {
457
- key: SHIBBOLETH_IDP_ISSUER_METADATA_KEY,
458
- value: '',
459
- note: 'Optional expected Shibboleth IdP entity ID. Leave empty to use the entityID from IdP metadata.',
460
- type: 'TEXT_SINGLE_LINE',
461
- },
462
- {
463
- key: SHIBBOLETH_ENTITY_ID_METADATA_KEY,
464
- value: '',
465
- note: 'Optional SAML Service Provider EntityID. Leave empty to use this server public URL plus /api/auth/shibboleth/metadata.',
466
- type: 'TEXT_SINGLE_LINE',
467
- },
468
- {
469
- key: SHIBBOLETH_CALLBACK_URL_METADATA_KEY,
470
- value: '',
471
- note: 'Optional SAML Assertion Consumer Service URL. Leave empty to use this server public URL plus /api/auth/shibboleth/acs.',
472
- type: 'TEXT_SINGLE_LINE',
473
- },
474
- {
475
- key: SHIBBOLETH_USERNAME_ATTRIBUTE_METADATA_KEY,
476
- value: DEFAULT_SHIBBOLETH_USERNAME_ATTRIBUTE,
477
- note: 'SAML attribute used as the Agents Server username. For Silesian University this is usually mail or unstructuredName.',
478
- type: 'TEXT_SINGLE_LINE',
479
- },
480
- {
481
- key: SHIBBOLETH_AUTO_CREATE_USERS_METADATA_KEY,
482
- value: 'true',
483
- note: 'Create a non-admin Agents Server user automatically after a valid Shibboleth login when no matching username exists.',
484
- type: 'BOOLEAN',
485
- },
486
- {
487
- key: SHIBBOLETH_PROVIDER_LABEL_METADATA_KEY,
488
- value: DEFAULT_SHIBBOLETH_PROVIDER_LABEL,
489
- note: 'User-facing label for the Shibboleth login button.',
490
- type: 'TEXT_SINGLE_LINE',
491
- },
492
466
  {
493
467
  key: DEFAULT_VISIBILITY_METADATA_KEY,
494
468
  value: 'UNLISTED',
@@ -5,8 +5,14 @@ import {
5
5
  resolveDatabaseMigrationRuntimeConfiguration,
6
6
  runDatabaseMigrations,
7
7
  } from './runDatabaseMigrations';
8
+ import { isAgentsServerSqliteMode } from './agentsServerDatabaseMode';
8
9
 
9
- dotenv.config();
10
+ /**
11
+ * Environment variable pointing to the installed Agents Server `.env` file.
12
+ */
13
+ const AGENTS_SERVER_ENV_FILE_ENV_NAME = 'PTBK_AGENTS_SERVER_ENV_FILE';
14
+
15
+ loadDatabaseMigrationEnvironment();
10
16
 
11
17
  /**
12
18
  * Runs manual migration command from CLI arguments.
@@ -14,6 +20,11 @@ dotenv.config();
14
20
  async function migrate(): Promise<void> {
15
21
  console.info(colors.bgBlue('🚀 Starting database migration'));
16
22
 
23
+ if (isAgentsServerSqliteMode()) {
24
+ console.info(colors.yellow('⏭️ Skipping PostgreSQL migrations because Agents Server uses local SQLite.'));
25
+ return;
26
+ }
27
+
17
28
  const runtimeConfiguration = await resolveDatabaseMigrationRuntimeConfiguration(console);
18
29
  if (!runtimeConfiguration) {
19
30
  return;
@@ -46,6 +57,24 @@ async function migrate(): Promise<void> {
46
57
  }
47
58
  }
48
59
 
60
+ /**
61
+ * Loads database migration environment variables.
62
+ *
63
+ * Self-update runs the migration command from the repository checkout, while
64
+ * the deployed Agents Server `.env` lives in the installation directory.
65
+ */
66
+ function loadDatabaseMigrationEnvironment(): void {
67
+ const explicitEnvFilePath = process.env[AGENTS_SERVER_ENV_FILE_ENV_NAME]?.trim();
68
+ if (explicitEnvFilePath) {
69
+ const explicitLoadResult = dotenv.config({ path: explicitEnvFilePath });
70
+ if (!explicitLoadResult.error) {
71
+ return;
72
+ }
73
+ }
74
+
75
+ dotenv.config();
76
+ }
77
+
49
78
  /**
50
79
  * Parses optional `--only` flag from CLI arguments.
51
80
  *
@@ -0,0 +1,136 @@
1
+ ALTER TABLE "prefix_User"
2
+ ADD COLUMN IF NOT EXISTS "email" TEXT NULL,
3
+ ADD COLUMN IF NOT EXISTS "displayName" TEXT NULL,
4
+ ADD COLUMN IF NOT EXISTS "authenticationProvider" TEXT NOT NULL DEFAULT 'LOCAL';
5
+
6
+ COMMENT ON COLUMN "prefix_User"."email" IS 'Email address used for external authentication providers such as Shibboleth.';
7
+ COMMENT ON COLUMN "prefix_User"."displayName" IS 'Human-readable display name from external authentication providers.';
8
+ COMMENT ON COLUMN "prefix_User"."authenticationProvider" IS 'Primary authentication provider marker. Values include LOCAL, SHIBBOLETH, and LOCAL_AND_SHIBBOLETH.';
9
+
10
+ CREATE TABLE IF NOT EXISTS "prefix_ShibbolethUserIdentity" (
11
+ "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
12
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
13
+ "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
14
+ "userId" BIGINT NOT NULL,
15
+ "email" TEXT NOT NULL,
16
+ "displayName" TEXT NULL,
17
+ "nameId" TEXT NULL,
18
+ "nameIdFormat" TEXT NULL,
19
+ "unstructuredName" TEXT NULL,
20
+ "eduPersonPrincipalName" TEXT NULL,
21
+ "rawAttributes" JSONB NULL,
22
+ "lastLoggedInAt" TIMESTAMP WITH TIME ZONE NULL,
23
+ "loginCount" BIGINT NOT NULL DEFAULT 0
24
+ );
25
+
26
+ COMMENT ON TABLE "prefix_ShibbolethUserIdentity" IS 'Link between Agents Server users and Shibboleth identities.';
27
+ COMMENT ON COLUMN "prefix_ShibbolethUserIdentity"."rawAttributes" IS 'Raw SAML assertion profile attributes received from Shibboleth.';
28
+
29
+ CREATE UNIQUE INDEX IF NOT EXISTS "prefix_ShibbolethUserIdentity_userId_idx"
30
+ ON "prefix_ShibbolethUserIdentity" ("userId");
31
+ CREATE UNIQUE INDEX IF NOT EXISTS "prefix_ShibbolethUserIdentity_email_idx"
32
+ ON "prefix_ShibbolethUserIdentity" ("email");
33
+ CREATE UNIQUE INDEX IF NOT EXISTS "prefix_ShibbolethUserIdentity_nameId_idx"
34
+ ON "prefix_ShibbolethUserIdentity" ("nameId");
35
+ CREATE INDEX IF NOT EXISTS "prefix_ShibbolethUserIdentity_lastLoggedInAt_idx"
36
+ ON "prefix_ShibbolethUserIdentity" ("lastLoggedInAt" DESC);
37
+
38
+ ALTER TABLE "prefix_ShibbolethUserIdentity"
39
+ DROP CONSTRAINT IF EXISTS "prefix_ShibbolethUserIdentity_userId_fkey",
40
+ ADD CONSTRAINT "prefix_ShibbolethUserIdentity_userId_fkey"
41
+ FOREIGN KEY ("userId")
42
+ REFERENCES "prefix_User"("id")
43
+ ON DELETE CASCADE;
44
+
45
+ ALTER TABLE "prefix_ShibbolethUserIdentity" ENABLE ROW LEVEL SECURITY;
46
+
47
+ CREATE TABLE IF NOT EXISTS "prefix_ShibbolethAuthenticationAttempt" (
48
+ "id" BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
49
+ "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(),
50
+ "stage" TEXT NOT NULL,
51
+ "status" TEXT NOT NULL,
52
+ "userId" BIGINT NULL,
53
+ "email" TEXT NULL,
54
+ "displayName" TEXT NULL,
55
+ "nameId" TEXT NULL,
56
+ "relayState" TEXT NULL,
57
+ "ip" TEXT NULL,
58
+ "userAgent" TEXT NULL,
59
+ "errorMessage" TEXT NULL,
60
+ "rawAttributes" JSONB NULL
61
+ );
62
+
63
+ COMMENT ON TABLE "prefix_ShibbolethAuthenticationAttempt" IS 'Audit log of Shibboleth authentication attempts.';
64
+ COMMENT ON COLUMN "prefix_ShibbolethAuthenticationAttempt"."stage" IS 'Authentication process stage such as LOGIN_REQUEST or ASSERTION_CONSUMER_SERVICE.';
65
+ COMMENT ON COLUMN "prefix_ShibbolethAuthenticationAttempt"."status" IS 'Attempt result such as STARTED, REDIRECTED, SUCCESS, FAILED, or REJECTED.';
66
+ COMMENT ON COLUMN "prefix_ShibbolethAuthenticationAttempt"."rawAttributes" IS 'Raw SAML assertion profile attributes when available.';
67
+
68
+ CREATE INDEX IF NOT EXISTS "prefix_ShibbolethAuthenticationAttempt_createdAt_idx"
69
+ ON "prefix_ShibbolethAuthenticationAttempt" ("createdAt" DESC);
70
+ CREATE INDEX IF NOT EXISTS "prefix_ShibbolethAuthenticationAttempt_email_idx"
71
+ ON "prefix_ShibbolethAuthenticationAttempt" ("email");
72
+
73
+ ALTER TABLE "prefix_ShibbolethAuthenticationAttempt"
74
+ DROP CONSTRAINT IF EXISTS "prefix_ShibbolethAuthenticationAttempt_userId_fkey",
75
+ ADD CONSTRAINT "prefix_ShibbolethAuthenticationAttempt_userId_fkey"
76
+ FOREIGN KEY ("userId")
77
+ REFERENCES "prefix_User"("id")
78
+ ON DELETE SET NULL;
79
+
80
+ ALTER TABLE "prefix_ShibbolethAuthenticationAttempt" ENABLE ROW LEVEL SECURITY;
81
+
82
+ INSERT INTO "prefix_Metadata" ("key", "value", "note", "createdAt", "updatedAt")
83
+ SELECT 'IS_SHIBBOLETH_AUTH_ACTIVE',
84
+ 'false',
85
+ 'Enable Shibboleth authentication alongside standard username/password login.',
86
+ NOW(),
87
+ NOW()
88
+ WHERE NOT EXISTS (SELECT 1 FROM "prefix_Metadata" WHERE "key" = 'IS_SHIBBOLETH_AUTH_ACTIVE');
89
+
90
+ INSERT INTO "prefix_Metadata" ("key", "value", "note", "createdAt", "updatedAt")
91
+ SELECT 'SHIBBOLETH_IDP_METADATA_URL',
92
+ '',
93
+ 'URL of the Shibboleth Identity Provider metadata XML. For Silesian University this is https://idp-cro.slu.cz/idp/shibboleth.',
94
+ NOW(),
95
+ NOW()
96
+ WHERE NOT EXISTS (SELECT 1 FROM "prefix_Metadata" WHERE "key" = 'SHIBBOLETH_IDP_METADATA_URL');
97
+
98
+ INSERT INTO "prefix_Metadata" ("key", "value", "note", "createdAt", "updatedAt")
99
+ SELECT 'SHIBBOLETH_IDP_METADATA_XML',
100
+ '',
101
+ 'Optional pasted Shibboleth Identity Provider metadata XML. Used instead of SHIBBOLETH_IDP_METADATA_URL when filled.',
102
+ NOW(),
103
+ NOW()
104
+ WHERE NOT EXISTS (SELECT 1 FROM "prefix_Metadata" WHERE "key" = 'SHIBBOLETH_IDP_METADATA_XML');
105
+
106
+ INSERT INTO "prefix_Metadata" ("key", "value", "note", "createdAt", "updatedAt")
107
+ SELECT 'SHIBBOLETH_SP_ENTITY_ID',
108
+ '',
109
+ 'Optional Service Provider entity ID override. When empty, Agents Server uses the generated Shibboleth metadata endpoint URL.',
110
+ NOW(),
111
+ NOW()
112
+ WHERE NOT EXISTS (SELECT 1 FROM "prefix_Metadata" WHERE "key" = 'SHIBBOLETH_SP_ENTITY_ID');
113
+
114
+ INSERT INTO "prefix_Metadata" ("key", "value", "note", "createdAt", "updatedAt")
115
+ SELECT 'SHIBBOLETH_EMAIL_ATTRIBUTE_NAMES',
116
+ 'mail email urn:oid:0.9.2342.19200300.100.1.3',
117
+ 'Space-separated SAML attribute names accepted as the user email during Shibboleth login.',
118
+ NOW(),
119
+ NOW()
120
+ WHERE NOT EXISTS (SELECT 1 FROM "prefix_Metadata" WHERE "key" = 'SHIBBOLETH_EMAIL_ATTRIBUTE_NAMES');
121
+
122
+ INSERT INTO "prefix_Metadata" ("key", "value", "note", "createdAt", "updatedAt")
123
+ SELECT 'SHIBBOLETH_DISPLAY_NAME_ATTRIBUTE_NAMES',
124
+ 'displayName urn:oid:2.16.840.1.113730.3.1.241',
125
+ 'Space-separated SAML attribute names accepted as the user display name during Shibboleth login.',
126
+ NOW(),
127
+ NOW()
128
+ WHERE NOT EXISTS (SELECT 1 FROM "prefix_Metadata" WHERE "key" = 'SHIBBOLETH_DISPLAY_NAME_ATTRIBUTE_NAMES');
129
+
130
+ INSERT INTO "prefix_Metadata" ("key", "value", "note", "createdAt", "updatedAt")
131
+ SELECT 'SHIBBOLETH_UNSTRUCTURED_NAME_ATTRIBUTE_NAMES',
132
+ 'unstructuredName eduPersonPrincipalName urn:oid:1.3.6.1.4.1.5923.1.1.1.6 uid',
133
+ 'Space-separated SAML attribute names accepted as the institutional identifier during Shibboleth login.',
134
+ NOW(),
135
+ NOW()
136
+ WHERE NOT EXISTS (SELECT 1 FROM "prefix_Metadata" WHERE "key" = 'SHIBBOLETH_UNSTRUCTURED_NAME_ATTRIBUTE_NAMES');
@@ -86,19 +86,14 @@ const JSON_COLUMNS_BY_TABLE = new Map<string, ReadonlySet<string>>([
86
86
  ['ShareTargetPayload', new Set(['attachments'])],
87
87
  ['CalendarConnection', new Set(['scopes'])],
88
88
  ['CalendarActivity', new Set(['details'])],
89
+ ['ShibbolethUserIdentity', new Set(['rawAttributes'])],
90
+ ['ShibbolethAuthenticationAttempt', new Set(['rawAttributes'])],
89
91
  ]);
90
92
 
91
93
  /**
92
94
  * Boolean columns stored as integers by SQLite and restored as booleans.
93
95
  */
94
- const BOOLEAN_COLUMNS = new Set([
95
- 'isAdmin',
96
- 'isRevoked',
97
- 'isGlobal',
98
- 'isUserScoped',
99
- 'isSuccessful',
100
- 'isChatFocused',
101
- ]);
96
+ const BOOLEAN_COLUMNS = new Set(['isAdmin', 'isRevoked', 'isGlobal', 'isUserScoped', 'isSuccessful', 'isChatFocused']);
102
97
 
103
98
  /**
104
99
  * Tables whose primary key is provided as text rather than generated numerically.
@@ -122,6 +117,7 @@ const UNIQUE_INDEX_COLUMNS_BY_TABLE = new Map<string, ReadonlyArray<ReadonlyArra
122
117
  ['AgentExternals', [['type', 'hash']]],
123
118
  ['VectorStoreKnowledgeSourceHashes', [['source']]],
124
119
  ['User', [['username']]],
120
+ ['ShibbolethUserIdentity', [['userId'], ['email'], ['nameId']]],
125
121
  ['UserChatJob', [['chatId', 'clientMessageId']]],
126
122
  ['LlmCache', [['hash']]],
127
123
  ['OpenAiAssistantCache', [['agentHash']]],
@@ -158,7 +154,9 @@ export function $provideLocalSqliteSupabase(): SupabaseClient {
158
154
  return localSqliteSupabase;
159
155
  }
160
156
 
161
- localSqliteSupabase = new LocalSqliteSupabaseClient($provideAgentsServerSqliteDatabase()) as unknown as SupabaseClient;
157
+ localSqliteSupabase = new LocalSqliteSupabaseClient(
158
+ $provideAgentsServerSqliteDatabase(),
159
+ ) as unknown as SupabaseClient;
162
160
  return localSqliteSupabase;
163
161
  }
164
162
 
@@ -188,10 +186,7 @@ class LocalSqliteSupabaseClient {
188
186
  * Supabase-shaped table entry point. Every operation starts a fresh query builder.
189
187
  */
190
188
  class LocalSqliteTable {
191
- public constructor(
192
- private readonly database: AgentsServerSqliteDatabase,
193
- private readonly tableName: string,
194
- ) {}
189
+ public constructor(private readonly database: AgentsServerSqliteDatabase, private readonly tableName: string) {}
195
190
 
196
191
  /**
197
192
  * Starts a select query.
@@ -248,10 +243,7 @@ class LocalSqliteQueryBuilder implements PromiseLike<LocalSqliteQueryResult> {
248
243
  private signal: AbortSignal | null = null;
249
244
  private isReturningSelection = false;
250
245
 
251
- public constructor(
252
- private readonly database: AgentsServerSqliteDatabase,
253
- private readonly tableName: string,
254
- ) {}
246
+ public constructor(private readonly database: AgentsServerSqliteDatabase, private readonly tableName: string) {}
255
247
 
256
248
  /**
257
249
  * Configures selected columns or mutation return columns.
@@ -569,10 +561,16 @@ class LocalSqliteQueryBuilder implements PromiseLike<LocalSqliteQueryResult> {
569
561
 
570
562
  if (rowids.length > 0 && updateColumns.length > 0) {
571
563
  const assignments = updateColumns.map((column) => `${quoteIdentifier(column)} = ?`).join(', ');
572
- const values = updateColumns.map((column) => serializeValue(this.tableName, column, this.mutationValues[column]));
564
+ const values = updateColumns.map((column) =>
565
+ serializeValue(this.tableName, column, this.mutationValues[column]),
566
+ );
573
567
  const rowidPlaceholders = rowids.map(() => '?').join(', ');
574
568
  this.database
575
- .prepare(`UPDATE ${quoteIdentifier(this.tableName)} SET ${assignments} WHERE rowid IN (${rowidPlaceholders})`)
569
+ .prepare(
570
+ `UPDATE ${quoteIdentifier(
571
+ this.tableName,
572
+ )} SET ${assignments} WHERE rowid IN (${rowidPlaceholders})`,
573
+ )
576
574
  .run(...values, ...rowids);
577
575
  }
578
576
 
@@ -591,7 +589,9 @@ class LocalSqliteQueryBuilder implements PromiseLike<LocalSqliteQueryResult> {
591
589
  const rowids = this.selectMatchingRowids();
592
590
  if (rowids.length > 0) {
593
591
  const rowidPlaceholders = rowids.map(() => '?').join(', ');
594
- this.database.prepare(`DELETE FROM ${quoteIdentifier(this.tableName)} WHERE rowid IN (${rowidPlaceholders})`).run(...rowids);
592
+ this.database
593
+ .prepare(`DELETE FROM ${quoteIdentifier(this.tableName)} WHERE rowid IN (${rowidPlaceholders})`)
594
+ .run(...rowids);
595
595
  }
596
596
 
597
597
  return this.createMutationResponse([]);
@@ -608,7 +608,10 @@ class LocalSqliteQueryBuilder implements PromiseLike<LocalSqliteQueryResult> {
608
608
  for (const rawRow of this.mutationRows) {
609
609
  const row = withInsertDefaults(tableBaseName, rawRow);
610
610
  ensureTable(this.database, this.tableName, [...Object.keys(row), ...conflictColumns]);
611
- const existingRowid = conflictColumns.length > 0 ? findConflictRowid(this.database, this.tableName, row, conflictColumns) : null;
611
+ const existingRowid =
612
+ conflictColumns.length > 0
613
+ ? findConflictRowid(this.database, this.tableName, row, conflictColumns)
614
+ : null;
612
615
 
613
616
  if (existingRowid !== null) {
614
617
  updateRowid(this.database, this.tableName, existingRowid, row);
@@ -634,7 +637,12 @@ class LocalSqliteQueryBuilder implements PromiseLike<LocalSqliteQueryResult> {
634
637
  };
635
638
  }
636
639
 
637
- const data = selectRowsByRowids(this.database, this.tableName, rowids, parseSelectedColumns(this.selectedColumns));
640
+ const data = selectRowsByRowids(
641
+ this.database,
642
+ this.tableName,
643
+ rowids,
644
+ parseSelectedColumns(this.selectedColumns),
645
+ );
638
646
  return this.finalizeDataResponse(data, null);
639
647
  }
640
648
 
@@ -824,7 +832,9 @@ function ensureTable(
824
832
  }
825
833
 
826
834
  database.exec(
827
- `ALTER TABLE ${quoteIdentifier(tableName)} ADD COLUMN ${quoteIdentifier(column)} ${resolveSqliteColumnType(column)}`,
835
+ `ALTER TABLE ${quoteIdentifier(tableName)} ADD COLUMN ${quoteIdentifier(column)} ${resolveSqliteColumnType(
836
+ column,
837
+ )}`,
828
838
  );
829
839
  existingColumns.add(column);
830
840
  }
@@ -841,7 +851,11 @@ function ensureUniqueIndexes(database: AgentsServerSqliteDatabase, tableName: st
841
851
  for (const columns of uniqueIndexes) {
842
852
  const indexName = `idx_${sanitizeSqlIdentifier(tableName)}_${columns.join('_')}_unique`;
843
853
  const columnSql = columns.map(quoteIdentifier).join(', ');
844
- database.exec(`CREATE UNIQUE INDEX IF NOT EXISTS ${quoteIdentifier(indexName)} ON ${quoteIdentifier(tableName)} (${columnSql})`);
854
+ database.exec(
855
+ `CREATE UNIQUE INDEX IF NOT EXISTS ${quoteIdentifier(indexName)} ON ${quoteIdentifier(
856
+ tableName,
857
+ )} (${columnSql})`,
858
+ );
845
859
  }
846
860
  }
847
861
 
@@ -861,7 +875,9 @@ function insertRow(
861
875
 
862
876
  const placeholders = columns.map(() => '?').join(', ');
863
877
  const values = columns.map((column) => serializeValue(tableName, column, row[column]));
864
- const sql = `INSERT INTO ${quoteIdentifier(tableName)} (${columns.map(quoteIdentifier).join(', ')}) VALUES (${placeholders})`;
878
+ const sql = `INSERT INTO ${quoteIdentifier(tableName)} (${columns
879
+ .map(quoteIdentifier)
880
+ .join(', ')}) VALUES (${placeholders})`;
865
881
 
866
882
  return database.prepare(sql).run(...values);
867
883
  }
@@ -900,7 +916,9 @@ function findConflictRowid(
900
916
 
901
917
  const conditions = conflictColumns.map((column) => `${quoteIdentifier(column)} = ?`).join(' AND ');
902
918
  const values = conflictColumns.map((column) => serializeValue(tableName, column, row[column]));
903
- const result = database.prepare(`SELECT rowid FROM ${quoteIdentifier(tableName)} WHERE ${conditions} LIMIT 1`).get(...values);
919
+ const result = database
920
+ .prepare(`SELECT rowid FROM ${quoteIdentifier(tableName)} WHERE ${conditions} LIMIT 1`)
921
+ .get(...values);
904
922
 
905
923
  return result ? (result.rowid as number | bigint) : null;
906
924
  }
@@ -922,7 +940,9 @@ function selectRowsByRowids(
922
940
  const placeholders = rowids.map(() => '?').join(', ');
923
941
  const rows = database
924
942
  .prepare(
925
- `SELECT ${createSelectExpression(selectedColumns)} FROM ${quoteIdentifier(tableName)} WHERE rowid IN (${placeholders})`,
943
+ `SELECT ${createSelectExpression(selectedColumns)} FROM ${quoteIdentifier(
944
+ tableName,
945
+ )} WHERE rowid IN (${placeholders})`,
926
946
  )
927
947
  .all(...rowids);
928
948
 
@@ -941,17 +961,25 @@ function createFilterCondition(
941
961
 
942
962
  switch (filter.operator) {
943
963
  case 'eq':
944
- return value === null ? { sql: `${column} IS NULL`, values: [] } : { sql: `${column} = ?`, values: [value] };
964
+ return value === null
965
+ ? { sql: `${column} IS NULL`, values: [] }
966
+ : { sql: `${column} = ?`, values: [value] };
945
967
  case 'neq':
946
968
  return value === null
947
969
  ? { sql: `${column} IS NOT NULL`, values: [] }
948
970
  : { sql: `${column} <> ?`, values: [value] };
949
971
  case 'is':
950
- return filter.value === null ? { sql: `${column} IS NULL`, values: [] } : { sql: `${column} IS ?`, values: [value] };
972
+ return filter.value === null
973
+ ? { sql: `${column} IS NULL`, values: [] }
974
+ : { sql: `${column} IS ?`, values: [value] };
951
975
  case 'not-is':
952
- return filter.value === null ? { sql: `${column} IS NOT NULL`, values: [] } : { sql: `${column} IS NOT ?`, values: [value] };
976
+ return filter.value === null
977
+ ? { sql: `${column} IS NOT NULL`, values: [] }
978
+ : { sql: `${column} IS NOT ?`, values: [value] };
953
979
  case 'in': {
954
- const values = Array.isArray(filter.value) ? filter.value.map((item) => serializeValue(tableName, filter.column, item)) : [];
980
+ const values = Array.isArray(filter.value)
981
+ ? filter.value.map((item) => serializeValue(tableName, filter.column, item))
982
+ : [];
955
983
  if (values.length === 0) {
956
984
  return { sql: '0 = 1', values: [] };
957
985
  }
@@ -1190,6 +1218,30 @@ function withInsertDefaults(tableBaseName: string, row: Record<string, unknown>)
1190
1218
  case 'User':
1191
1219
  result.isAdmin ??= false;
1192
1220
  result.profileImageUrl ??= null;
1221
+ result.email ??= null;
1222
+ result.displayName ??= null;
1223
+ result.authenticationProvider ??= 'LOCAL';
1224
+ break;
1225
+ case 'ShibbolethUserIdentity':
1226
+ result.displayName ??= null;
1227
+ result.nameId ??= null;
1228
+ result.nameIdFormat ??= null;
1229
+ result.unstructuredName ??= null;
1230
+ result.eduPersonPrincipalName ??= null;
1231
+ result.rawAttributes ??= null;
1232
+ result.lastLoggedInAt ??= null;
1233
+ result.loginCount ??= 0;
1234
+ break;
1235
+ case 'ShibbolethAuthenticationAttempt':
1236
+ result.userId ??= null;
1237
+ result.email ??= null;
1238
+ result.displayName ??= null;
1239
+ result.nameId ??= null;
1240
+ result.relayState ??= null;
1241
+ result.ip ??= null;
1242
+ result.userAgent ??= null;
1243
+ result.errorMessage ??= null;
1244
+ result.rawAttributes ??= null;
1193
1245
  break;
1194
1246
  case 'UserChat':
1195
1247
  result.messages ??= [];
@@ -1342,10 +1394,7 @@ function resolveUniqueIndexColumns(tableBaseName: string): Array<string> {
1342
1394
  /**
1343
1395
  * Resolves upsert conflict columns.
1344
1396
  */
1345
- function resolveUpsertConflictColumns(
1346
- tableBaseName: string,
1347
- options: LocalSqliteUpsertOptions,
1348
- ): ReadonlyArray<string> {
1397
+ function resolveUpsertConflictColumns(tableBaseName: string, options: LocalSqliteUpsertOptions): ReadonlyArray<string> {
1349
1398
  if (options.onConflict) {
1350
1399
  return options.onConflict
1351
1400
  .split(',')
@@ -1367,7 +1416,10 @@ function normalizeSqliteError(error: unknown): LocalSqliteError {
1367
1416
  : undefined;
1368
1417
 
1369
1418
  return {
1370
- code: sqliteCode === 'SQLITE_CONSTRAINT_UNIQUE' || sqliteCode === 'SQLITE_CONSTRAINT_PRIMARYKEY' ? '23505' : sqliteCode,
1419
+ code:
1420
+ sqliteCode === 'SQLITE_CONSTRAINT_UNIQUE' || sqliteCode === 'SQLITE_CONSTRAINT_PRIMARYKEY'
1421
+ ? '23505'
1422
+ : sqliteCode,
1371
1423
  message,
1372
1424
  };
1373
1425
  }
@@ -128,6 +128,7 @@ export const SERVER_TRANSLATION_KEYS = [
128
128
  'header.myAccount',
129
129
  'header.superAdmin',
130
130
  'header.administration',
131
+ 'header.loginMethods',
131
132
  'header.monitoringAndUsage',
132
133
  'header.integrationsAndKeys',
133
134
  'header.developerDebug',
@@ -168,6 +169,7 @@ export const SERVER_TRANSLATION_KEYS = [
168
169
  'header.imagesGallery',
169
170
  'header.files',
170
171
  'header.users',
172
+ 'header.shibboleth',
171
173
  'header.versionInfo',
172
174
  'header.experiments',
173
175
  'header.story',
@@ -198,8 +200,8 @@ export const SERVER_TRANSLATION_KEYS = [
198
200
  'login.passwordPlaceholder',
199
201
  'login.loggingIn',
200
202
  'login.loginAction',
201
- 'login.shibbolethAction',
202
- 'login.methodDivider',
203
+ 'login.shibbolethLoginAction',
204
+ 'login.shibbolethNotConfigured',
203
205
  'login.forgottenPassword',
204
206
  'login.registerNewUser',
205
207
  'login.errorOccurred',
@@ -122,6 +122,7 @@ header.menuLabel: Nabídka
122
122
  header.myAccount: Můj účet
123
123
  header.superAdmin: Super administrace
124
124
  header.administration: Administrace
125
+ header.loginMethods: Metody přihlášení
125
126
  header.monitoringAndUsage: Monitoring a využití
126
127
  header.integrationsAndKeys: Integrace a klíče
127
128
  header.developerDebug: Vývoj a ladění
@@ -162,6 +163,7 @@ header.errorSimulation: Simulace chyb
162
163
  header.imagesGallery: Galerie obrázků
163
164
  header.files: Soubory
164
165
  header.users: Uživatelé
166
+ header.shibboleth: Shibboleth
165
167
  header.versionInfo: Informace o verzi
166
168
  header.experiments: Experimenty
167
169
  header.story: Příběh
@@ -192,8 +194,8 @@ login.passwordLabel: Heslo
192
194
  login.passwordPlaceholder: Zadejte heslo
193
195
  login.loggingIn: Přihlašuji…
194
196
  login.loginAction: Přihlásit se
195
- login.shibbolethAction: Pokračovat přes {provider}
196
- login.methodDivider: nebo
197
+ login.shibbolethLoginAction: Pokračovat přes Shibboleth
198
+ login.shibbolethNotConfigured: Přihlášení přes Shibboleth je aktivní, ale vyžaduje nastavení správcem.
197
199
  login.forgottenPassword: Zapomněli jste heslo?
198
200
  login.registerNewUser: Zaregistrovat nového uživatele
199
201
  login.errorOccurred: Došlo k chybě