Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions apps/host-selfhost/src/auth/better-auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,47 @@ test("sign-up issues a bearer token and resolves to a per-user org-pinned identi
expect(body.organization!.id).toBeTruthy();
});

test("self-host API keys are not capped by Better Auth's default request limit", async () => {
const inviteCode = await mintInviteCode(handler);
const signUp = await handler(
new Request(`${BASE}/api/auth/sign-up/email`, {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({
email: "key-user@test.local",
password: "member-password-123",
name: "Key User",
inviteCode,
}),
}),
);
expect(signUp.status).toBe(200);
const token = signUp.headers.get("set-auth-token");
expect(token).toBeTruthy();

const createKey = await handler(
new Request(`${BASE}/api/account/api-keys`, {
method: "POST",
headers: {
authorization: `Bearer ${token}`,
"content-type": "application/json",
},
body: JSON.stringify({ name: "MCP bootstrap" }),
}),
);
expect(createKey.status).toBe(200);
const keyBody = (await createKey.json()) as { value: string };

for (let i = 0; i < 12; i++) {
const me = await handler(
new Request(`${BASE}/api/account/me`, {
headers: { "x-api-key": keyBody.value },
}),
);
expect(me.status).toBe(200);
}
});

test("an unauthenticated request is rejected with 401", async () => {
const res = await handler(new Request("http://localhost/api/account/me"));
expect(res.status).toBe(401);
Expand Down
2 changes: 1 addition & 1 deletion apps/host-selfhost/src/auth/better-auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ const makeAuthOptions = (url: string, organizationId: string, gate?: SignupGate)
plugins: [
organization(),
admin(),
apiKey({ enableSessionForAPIKeys: true }),
apiKey({ enableSessionForAPIKeys: true, rateLimit: { enabled: false } }),
bearer(),
mcp({ loginPage: "/login" }),
],
Expand Down
43 changes: 43 additions & 0 deletions apps/host-selfhost/web/routeTree.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ import { Route as IndexRouteImport } from './routes/index'
import { Route as SourcesNamespaceRouteImport } from './routes/sources.$namespace'
import { Route as ResumeExecutionIdRouteImport } from './routes/resume.$executionId'
import { Route as JoinCodeRouteImport } from './routes/join.$code'
import { Route as IntegrationsNamespaceRouteImport } from './routes/integrations.$namespace'
import { Route as SourcesAddPluginKeyRouteImport } from './routes/sources.add.$pluginKey'
import { Route as PluginsPluginIdSplatRouteImport } from './routes/plugins.$pluginId.$'
import { Route as IntegrationsAddPluginKeyRouteImport } from './routes/integrations.add.$pluginKey'

const ToolsRoute = ToolsRouteImport.update({
id: '/tools',
Expand Down Expand Up @@ -66,6 +68,11 @@ const JoinCodeRoute = JoinCodeRouteImport.update({
path: '/join/$code',
getParentRoute: () => rootRouteImport,
} as any)
const IntegrationsNamespaceRoute = IntegrationsNamespaceRouteImport.update({
id: '/integrations/$namespace',
path: '/integrations/$namespace',
getParentRoute: () => rootRouteImport,
} as any)
const SourcesAddPluginKeyRoute = SourcesAddPluginKeyRouteImport.update({
id: '/sources/add/$pluginKey',
path: '/sources/add/$pluginKey',
Expand All @@ -76,6 +83,12 @@ const PluginsPluginIdSplatRoute = PluginsPluginIdSplatRouteImport.update({
path: '/plugins/$pluginId/$',
getParentRoute: () => rootRouteImport,
} as any)
const IntegrationsAddPluginKeyRoute =
IntegrationsAddPluginKeyRouteImport.update({
id: '/integrations/add/$pluginKey',
path: '/integrations/add/$pluginKey',
getParentRoute: () => rootRouteImport,
} as any)

export interface FileRoutesByFullPath {
'/': typeof IndexRoute
Expand All @@ -84,9 +97,11 @@ export interface FileRoutesByFullPath {
'/policies': typeof PoliciesRoute
'/secrets': typeof SecretsRoute
'/tools': typeof ToolsRoute
'/integrations/$namespace': typeof IntegrationsNamespaceRoute
'/join/$code': typeof JoinCodeRoute
'/resume/$executionId': typeof ResumeExecutionIdRoute
'/sources/$namespace': typeof SourcesNamespaceRoute
'/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
'/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
'/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
}
Expand All @@ -97,9 +112,11 @@ export interface FileRoutesByTo {
'/policies': typeof PoliciesRoute
'/secrets': typeof SecretsRoute
'/tools': typeof ToolsRoute
'/integrations/$namespace': typeof IntegrationsNamespaceRoute
'/join/$code': typeof JoinCodeRoute
'/resume/$executionId': typeof ResumeExecutionIdRoute
'/sources/$namespace': typeof SourcesNamespaceRoute
'/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
'/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
'/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
}
Expand All @@ -111,9 +128,11 @@ export interface FileRoutesById {
'/policies': typeof PoliciesRoute
'/secrets': typeof SecretsRoute
'/tools': typeof ToolsRoute
'/integrations/$namespace': typeof IntegrationsNamespaceRoute
'/join/$code': typeof JoinCodeRoute
'/resume/$executionId': typeof ResumeExecutionIdRoute
'/sources/$namespace': typeof SourcesNamespaceRoute
'/integrations/add/$pluginKey': typeof IntegrationsAddPluginKeyRoute
'/plugins/$pluginId/$': typeof PluginsPluginIdSplatRoute
'/sources/add/$pluginKey': typeof SourcesAddPluginKeyRoute
}
Expand All @@ -126,9 +145,11 @@ export interface FileRouteTypes {
| '/policies'
| '/secrets'
| '/tools'
| '/integrations/$namespace'
| '/join/$code'
| '/resume/$executionId'
| '/sources/$namespace'
| '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
| '/sources/add/$pluginKey'
fileRoutesByTo: FileRoutesByTo
Expand All @@ -139,9 +160,11 @@ export interface FileRouteTypes {
| '/policies'
| '/secrets'
| '/tools'
| '/integrations/$namespace'
| '/join/$code'
| '/resume/$executionId'
| '/sources/$namespace'
| '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
| '/sources/add/$pluginKey'
id:
Expand All @@ -152,9 +175,11 @@ export interface FileRouteTypes {
| '/policies'
| '/secrets'
| '/tools'
| '/integrations/$namespace'
| '/join/$code'
| '/resume/$executionId'
| '/sources/$namespace'
| '/integrations/add/$pluginKey'
| '/plugins/$pluginId/$'
| '/sources/add/$pluginKey'
fileRoutesById: FileRoutesById
Expand All @@ -166,9 +191,11 @@ export interface RootRouteChildren {
PoliciesRoute: typeof PoliciesRoute
SecretsRoute: typeof SecretsRoute
ToolsRoute: typeof ToolsRoute
IntegrationsNamespaceRoute: typeof IntegrationsNamespaceRoute
JoinCodeRoute: typeof JoinCodeRoute
ResumeExecutionIdRoute: typeof ResumeExecutionIdRoute
SourcesNamespaceRoute: typeof SourcesNamespaceRoute
IntegrationsAddPluginKeyRoute: typeof IntegrationsAddPluginKeyRoute
PluginsPluginIdSplatRoute: typeof PluginsPluginIdSplatRoute
SourcesAddPluginKeyRoute: typeof SourcesAddPluginKeyRoute
}
Expand Down Expand Up @@ -238,6 +265,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof JoinCodeRouteImport
parentRoute: typeof rootRouteImport
}
'/integrations/$namespace': {
id: '/integrations/$namespace'
path: '/integrations/$namespace'
fullPath: '/integrations/$namespace'
preLoaderRoute: typeof IntegrationsNamespaceRouteImport
parentRoute: typeof rootRouteImport
}
'/sources/add/$pluginKey': {
id: '/sources/add/$pluginKey'
path: '/sources/add/$pluginKey'
Expand All @@ -252,6 +286,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof PluginsPluginIdSplatRouteImport
parentRoute: typeof rootRouteImport
}
'/integrations/add/$pluginKey': {
id: '/integrations/add/$pluginKey'
path: '/integrations/add/$pluginKey'
fullPath: '/integrations/add/$pluginKey'
preLoaderRoute: typeof IntegrationsAddPluginKeyRouteImport
parentRoute: typeof rootRouteImport
}
}
}

Expand All @@ -262,9 +303,11 @@ const rootRouteChildren: RootRouteChildren = {
PoliciesRoute: PoliciesRoute,
SecretsRoute: SecretsRoute,
ToolsRoute: ToolsRoute,
IntegrationsNamespaceRoute: IntegrationsNamespaceRoute,
JoinCodeRoute: JoinCodeRoute,
ResumeExecutionIdRoute: ResumeExecutionIdRoute,
SourcesNamespaceRoute: SourcesNamespaceRoute,
IntegrationsAddPluginKeyRoute: IntegrationsAddPluginKeyRoute,
PluginsPluginIdSplatRoute: PluginsPluginIdSplatRoute,
SourcesAddPluginKeyRoute: SourcesAddPluginKeyRoute,
}
Expand Down
9 changes: 9 additions & 0 deletions apps/host-selfhost/web/routes/integrations.$namespace.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { createFileRoute } from "@tanstack/react-router";
import { IntegrationDetailPage } from "@executor-js/react/pages/integration-detail";

export const Route = createFileRoute("/integrations/$namespace")({
component: () => {
const { namespace } = Route.useParams();
return <IntegrationDetailPage namespace={namespace} />;
},
});
22 changes: 22 additions & 0 deletions apps/host-selfhost/web/routes/integrations.add.$pluginKey.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Schema } from "effect";
import { createFileRoute } from "@tanstack/react-router";
import { AddIntegrationPage } from "@executor-js/react/pages/integration-add";

const SearchParams = Schema.toStandardSchemaV1(
Schema.Struct({
url: Schema.optional(Schema.String),
preset: Schema.optional(Schema.String),
namespace: Schema.optional(Schema.String),
}),
);

export const Route = createFileRoute("/integrations/add/$pluginKey")({
validateSearch: SearchParams,
component: () => {
const { pluginKey } = Route.useParams();
const { url, preset, namespace } = Route.useSearch();
return (
<AddIntegrationPage pluginKey={pluginKey} url={url} preset={preset} namespace={namespace} />
);
},
});
10 changes: 5 additions & 5 deletions apps/host-selfhost/web/routes/sources.$namespace.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { createFileRoute } from "@tanstack/react-router";
import { IntegrationDetailPage } from "@executor-js/react/pages/integration-detail";
import { createFileRoute, redirect } from "@tanstack/react-router";

export const Route = createFileRoute("/sources/$namespace")({
component: () => {
const { namespace } = Route.useParams();
return <IntegrationDetailPage namespace={namespace} />;
beforeLoad: ({ params }) => {
const { namespace } = params;
// oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router redirects are modeled as thrown values
throw redirect({ to: "/integrations/$namespace", params: { namespace } });
},
});
13 changes: 5 additions & 8 deletions apps/host-selfhost/web/routes/sources.add.$pluginKey.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Schema } from "effect";
import { createFileRoute } from "@tanstack/react-router";
import { AddIntegrationPage } from "@executor-js/react/pages/integration-add";
import { createFileRoute, redirect } from "@tanstack/react-router";

const SearchParams = Schema.toStandardSchemaV1(
Schema.Struct({
Expand All @@ -12,11 +11,9 @@ const SearchParams = Schema.toStandardSchemaV1(

export const Route = createFileRoute("/sources/add/$pluginKey")({
validateSearch: SearchParams,
component: () => {
const { pluginKey } = Route.useParams();
const { url, preset, namespace } = Route.useSearch();
return (
<AddIntegrationPage pluginKey={pluginKey} url={url} preset={preset} namespace={namespace} />
);
beforeLoad: ({ params, search }) => {
const { pluginKey } = params;
// oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: TanStack Router redirects are modeled as thrown values
throw redirect({ to: "/integrations/add/$pluginKey", params: { pluginKey }, search });
},
});
Loading