@protobase/server 0.12.1 → 0.12.3
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-options.d.ts +6 -0
- package/dist/assistant/assistant-settings.js +2 -8
- package/dist/auth/jwt.d.ts +4 -2
- package/dist/better-auth/app-tokens.d.ts +25 -0
- package/dist/better-auth/app-tokens.js +62 -0
- package/dist/better-auth/authenticator.d.ts +4 -1
- package/dist/better-auth/authenticator.js +4 -4
- package/dist/checked-url.js +10 -0
- package/dist/create-admin.js +7 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/meta.js +4 -1
- package/dist/runtime/runtime-settings.js +1 -1
- package/dist/sources/source-settings.d.ts +37 -0
- package/dist/sources/source-settings.js +89 -0
- package/package.json +4 -4
package/dist/admin-options.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AuditQueue, PipelineHook, ScanGuardMode } from "./types.js";
|
|
2
2
|
import { PluginOption } from "./plugins/plugin-settings.js";
|
|
3
|
+
import { SourceOption } from "./sources/source-settings.js";
|
|
3
4
|
import { CorsOrigins } from "./api-cors.js";
|
|
4
5
|
import { AssistantOptions } from "./assistant/assistant-settings.js";
|
|
5
6
|
import { RuntimeOptions } from "./runtime/runtime-settings.js";
|
|
@@ -45,5 +46,10 @@ export type AdminOptions = {
|
|
|
45
46
|
* `false` turns both off.
|
|
46
47
|
*/
|
|
47
48
|
plugins?: PluginOption[] | false;
|
|
49
|
+
/**
|
|
50
|
+
* Other Protobase APIs whose resources the admin app draws as its own (see `SourceOption`), before the ones the
|
|
51
|
+
* platform lists in `PROTOBASE_SOURCES`; `false` turns both off.
|
|
52
|
+
*/
|
|
53
|
+
sources?: SourceOption[] | false;
|
|
48
54
|
};
|
|
49
55
|
//#endregion
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { checkedUrl } from "../checked-url.js";
|
|
1
2
|
//#region src/assistant/assistant-settings.ts
|
|
2
3
|
const assistantVariables = {
|
|
3
4
|
url: "PROTOBASE_ASSISTANT_URL",
|
|
@@ -13,13 +14,6 @@ const openRouterModel = "openrouter/auto";
|
|
|
13
14
|
/** The roles that see the assistant: `/meta` names it, and the built-in one answers, only for a caller with one of them. */
|
|
14
15
|
const assistantRoles = ["admin", "ai"];
|
|
15
16
|
const seesAssistant = (roles) => assistantRoles.some((role) => roles.includes(role));
|
|
16
|
-
/** `url` without a trailing slash; anything but an http(s) URL stops the server, naming `source`. */
|
|
17
|
-
const checkedUrl = (url, source) => {
|
|
18
|
-
if (!URL.canParse(url)) throw new Error(`${source} is not a URL; use https://...`);
|
|
19
|
-
const { protocol } = new URL(url);
|
|
20
|
-
if (protocol !== "https:" && protocol !== "http:") throw new Error(`${source} must start with https:// or http://, not ${protocol}//`);
|
|
21
|
-
return url.replace(/\/+$/, "");
|
|
22
|
-
};
|
|
23
17
|
/**
|
|
24
18
|
* Which assistant the app has: the backend at `url` when one is set, otherwise the built-in one when an API key is
|
|
25
19
|
* set, otherwise none (also when the option is `false`). The built-in one calls OpenRouter unless `baseUrl` names
|
|
@@ -53,4 +47,4 @@ const assistantSettings = (option, env) => {
|
|
|
53
47
|
};
|
|
54
48
|
};
|
|
55
49
|
//#endregion
|
|
56
|
-
export { assistantRoles, assistantSettings, assistantVariables,
|
|
50
|
+
export { assistantRoles, assistantSettings, assistantVariables, defaultChatsDirectory, openRouterModel, openRouterUrl, seesAssistant };
|
package/dist/auth/jwt.d.ts
CHANGED
|
@@ -5,8 +5,10 @@ export type JwtOptions = {
|
|
|
5
5
|
/** JWKS endpoint of the identity provider; or pass `keys`, for example `createLocalJWKSet`. */
|
|
6
6
|
jwksUrl?: string | URL;
|
|
7
7
|
keys?: JWTVerifyGetKey;
|
|
8
|
-
issuer
|
|
9
|
-
|
|
8
|
+
/** The accepted issuer, or several. */
|
|
9
|
+
issuer?: string | string[];
|
|
10
|
+
/** The accepted audience, or several. */
|
|
11
|
+
audience?: string | string[];
|
|
10
12
|
/** Claim holding the roles, an array, or a string separated by spaces or commas. Default `roles`. */
|
|
11
13
|
rolesClaim?: string;
|
|
12
14
|
/** A second claim whose roles are added to those of `rolesClaim`, such as an organization membership's. */
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { Authenticator, TenantValue } from "../types.js";
|
|
2
|
+
import { JWTVerifyGetKey } from "jose";
|
|
3
|
+
//#region src/better-auth/app-tokens.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Another Protobase app whose signed-in users this API admits with the tokens that app issues, so its admin app can
|
|
6
|
+
* draw this API's resources (see `options.sources` there). `issuer` is that app's Better Auth `baseURL`, the issuer and
|
|
7
|
+
* audience of its tokens; its keys are read from `<issuer>/api/auth/jwks`. `roles` gives its global roles the ones they
|
|
8
|
+
* are here, `{ admin: 'shop' }`: a role it does not list is dropped, and a user left with none is refused. A role held
|
|
9
|
+
* in one of its organizations is never mapped: its members could hand it out. Its users work in `tenant` when the
|
|
10
|
+
* resources have one; nothing in its tokens names a tenant here. `keys` replaces reading its key set, for tests or keys
|
|
11
|
+
* at hand.
|
|
12
|
+
*/
|
|
13
|
+
export type TrustedApp = {
|
|
14
|
+
issuer: string;
|
|
15
|
+
roles: Record<string, string | string[]>;
|
|
16
|
+
tenant?: TenantValue;
|
|
17
|
+
keys?: JWTVerifyGetKey;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Admits the users of the `apps` this API trusts beside its own: a token whose issuer is one of theirs is verified
|
|
21
|
+
* against that app's keys, its roles given theirs here and its user id made `<sub>@<that app's host>`, unique beside this
|
|
22
|
+
* app's own users; every other token goes to `own`. A trusted app cannot have `ownIssuer`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const withTrustedApps: (own: Authenticator, ownIssuer: string, apps: readonly TrustedApp[]) => Authenticator;
|
|
25
|
+
//#endregion
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { checkedUrl } from "../checked-url.js";
|
|
2
|
+
import { forbidden } from "../problem.js";
|
|
3
|
+
import { noGlobalRole } from "./organizations/role-definitions.js";
|
|
4
|
+
import "./create-auth.js";
|
|
5
|
+
import { jwtAuthenticator } from "../auth/jwt.js";
|
|
6
|
+
import { createRemoteJWKSet, decodeJwt, errors } from "jose";
|
|
7
|
+
//#region src/better-auth/app-tokens.ts
|
|
8
|
+
const claimedIssuer = (request) => {
|
|
9
|
+
const [scheme, token] = (request.headers.get("authorization") ?? "").split(" ");
|
|
10
|
+
if (scheme?.toLowerCase() !== "bearer" || !token) return void 0;
|
|
11
|
+
try {
|
|
12
|
+
const { iss } = decodeJwt(token);
|
|
13
|
+
return typeof iss === "string" ? iss.replace(/\/+$/, "") : void 0;
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (error instanceof errors.JOSEError) return void 0;
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
const trustedAuthenticator = (app, issuer) => {
|
|
20
|
+
const forms = [issuer, `${issuer}/`];
|
|
21
|
+
const verify = jwtAuthenticator({
|
|
22
|
+
keys: app.keys ?? createRemoteJWKSet(new URL(`/api/auth/jwks`, issuer)),
|
|
23
|
+
issuer: forms,
|
|
24
|
+
audience: forms,
|
|
25
|
+
rolesClaim: "role",
|
|
26
|
+
tenantClaim: "org"
|
|
27
|
+
});
|
|
28
|
+
const host = new URL(issuer).host;
|
|
29
|
+
return async (request) => {
|
|
30
|
+
const { user, tenant: organization } = await verify(request);
|
|
31
|
+
const global = organization === void 0 ? user.roles : user.roles.filter((role) => role !== noGlobalRole);
|
|
32
|
+
const roles = [...new Set(global.flatMap((role) => Object.hasOwn(app.roles, role) ? app.roles[role] : []))];
|
|
33
|
+
if (roles.length === 0) throw forbidden("no-role", `Your role in ${host} gives you no role here`);
|
|
34
|
+
return {
|
|
35
|
+
user: {
|
|
36
|
+
id: `${user.id}@${host}`,
|
|
37
|
+
roles
|
|
38
|
+
},
|
|
39
|
+
...app.tenant !== void 0 && { tenant: app.tenant }
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Admits the users of the `apps` this API trusts beside its own: a token whose issuer is one of theirs is verified
|
|
45
|
+
* against that app's keys, its roles given theirs here and its user id made `<sub>@<that app's host>`, unique beside this
|
|
46
|
+
* app's own users; every other token goes to `own`. A trusted app cannot have `ownIssuer`.
|
|
47
|
+
*/
|
|
48
|
+
const withTrustedApps = (own, ownIssuer, apps) => {
|
|
49
|
+
if (apps.length === 0) return own;
|
|
50
|
+
const byIssuer = new Map(apps.map((app) => {
|
|
51
|
+
const issuer = checkedUrl(app.issuer.trim(), `The trusted app's issuer "${app.issuer}"`);
|
|
52
|
+
if (issuer === checkedUrl(ownIssuer, "issuer")) throw new Error(`The trusted app ${issuer} is this app itself; its own tokens are admitted already`);
|
|
53
|
+
return [issuer, trustedAuthenticator(app, issuer)];
|
|
54
|
+
}));
|
|
55
|
+
return (request) => {
|
|
56
|
+
const issuer = claimedIssuer(request);
|
|
57
|
+
const trusted = issuer === void 0 ? void 0 : byIssuer.get(issuer);
|
|
58
|
+
return trusted ? trusted(request) : own(request);
|
|
59
|
+
};
|
|
60
|
+
};
|
|
61
|
+
//#endregion
|
|
62
|
+
export { withTrustedApps };
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AdminAuth } from "./create-auth.js";
|
|
2
2
|
import { Authenticator, TenantValue } from "../types.js";
|
|
3
|
+
import { TrustedApp } from "./app-tokens.js";
|
|
3
4
|
import { JwksCacheOptions } from "./jwks-cache.js";
|
|
4
5
|
//#region src/better-auth/authenticator.d.ts
|
|
5
6
|
export type BetterAuthAuthenticatorOptions = {
|
|
@@ -15,12 +16,14 @@ export type BetterAuthAuthenticatorOptions = {
|
|
|
15
16
|
organizations?: boolean;
|
|
16
17
|
/** How long the key set is cached; see `cachedKeys`. */
|
|
17
18
|
cache?: JwksCacheOptions;
|
|
19
|
+
/** Other Protobase apps whose users this API admits with their own tokens, so their admin apps can draw its resources (see `TrustedApp`). */
|
|
20
|
+
apps?: TrustedApp[];
|
|
18
21
|
};
|
|
19
22
|
/**
|
|
20
23
|
* Verifies the bearer JWT that Better Auth's jwt plugin issues. `sub` is the user id, the `role` claim (a comma
|
|
21
24
|
* separated string from the admin plugin) becomes `roles`. With organizations, the `org` claim is the tenant and the
|
|
22
25
|
* organization, and the membership's `app_roles` are added to the global roles; `user`, which stands for no global
|
|
23
|
-
* role, is left out.
|
|
26
|
+
* role, is left out. With `apps`, the tokens of those apps are admitted too, with the roles each gives its users here.
|
|
24
27
|
*/
|
|
25
28
|
export declare const betterAuthAuthenticator: (options: BetterAuthAuthenticatorOptions) => Authenticator;
|
|
26
29
|
//#endregion
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { noGlobalRole } from "./organizations/role-definitions.js";
|
|
2
2
|
import { jwtAuthenticator } from "../auth/jwt.js";
|
|
3
|
+
import { withTrustedApps } from "./app-tokens.js";
|
|
3
4
|
import { cachedKeys } from "./jwks-cache.js";
|
|
4
5
|
//#region src/better-auth/authenticator.ts
|
|
5
6
|
/**
|
|
6
7
|
* Verifies the bearer JWT that Better Auth's jwt plugin issues. `sub` is the user id, the `role` claim (a comma
|
|
7
8
|
* separated string from the admin plugin) becomes `roles`. With organizations, the `org` claim is the tenant and the
|
|
8
9
|
* organization, and the membership's `app_roles` are added to the global roles; `user`, which stands for no global
|
|
9
|
-
* role, is left out.
|
|
10
|
+
* role, is left out. With `apps`, the tokens of those apps are admitted too, with the roles each gives its users here.
|
|
10
11
|
*/
|
|
11
12
|
const betterAuthAuthenticator = (options) => {
|
|
12
13
|
const issuer = options.issuer ?? options.auth?.issuer;
|
|
@@ -24,8 +25,7 @@ const betterAuthAuthenticator = (options) => {
|
|
|
24
25
|
},
|
|
25
26
|
...options.tenant !== void 0 && { tenant: options.tenant }
|
|
26
27
|
});
|
|
27
|
-
|
|
28
|
-
return async (request) => {
|
|
28
|
+
return withTrustedApps(!organizations ? verify : async (request) => {
|
|
29
29
|
const session = await verify(request);
|
|
30
30
|
const user = {
|
|
31
31
|
...session.user,
|
|
@@ -36,7 +36,7 @@ const betterAuthAuthenticator = (options) => {
|
|
|
36
36
|
user,
|
|
37
37
|
...session.tenant !== void 0 && { organization: { id: session.tenant } }
|
|
38
38
|
};
|
|
39
|
-
};
|
|
39
|
+
}, issuer, options.apps ?? []);
|
|
40
40
|
};
|
|
41
41
|
//#endregion
|
|
42
42
|
export { betterAuthAuthenticator };
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
//#region src/checked-url.ts
|
|
2
|
+
/** `url` without a trailing slash; anything but an http(s) URL stops the server, naming `source`. */
|
|
3
|
+
const checkedUrl = (url, source) => {
|
|
4
|
+
if (!URL.canParse(url)) throw new Error(`${source} is not a URL; use https://...`);
|
|
5
|
+
const { protocol } = new URL(url);
|
|
6
|
+
if (protocol !== "https:" && protocol !== "http:") throw new Error(`${source} must start with https:// or http://, not ${protocol}//`);
|
|
7
|
+
return url.replace(/\/+$/, "");
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
export { checkedUrl };
|
package/dist/create-admin.js
CHANGED
|
@@ -21,6 +21,7 @@ import { fileRoutes } from "./files/routes.js";
|
|
|
21
21
|
import { functionRoutes } from "./functions/routes.js";
|
|
22
22
|
import { pluginOptions } from "./plugins/plugin-settings.js";
|
|
23
23
|
import { runtimeUrl } from "./runtime/runtime-settings.js";
|
|
24
|
+
import { checkSourceNames, sourceOptions } from "./sources/source-settings.js";
|
|
24
25
|
import { createMeta } from "./meta.js";
|
|
25
26
|
import { buildRegistry } from "./registry.js";
|
|
26
27
|
import { batchRoute } from "./routes/batch.js";
|
|
@@ -60,6 +61,11 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
60
61
|
const assistant = assistantSettings(options.assistant, globalThis.process?.env ?? {});
|
|
61
62
|
const runtime = runtimeUrl(options.runtime, globalThis.process?.env ?? {});
|
|
62
63
|
const plugins = pluginOptions(options.plugins, globalThis.process?.env ?? {}, bundlePlugins);
|
|
64
|
+
const sources = sourceOptions(options.sources, globalThis.process?.env ?? {});
|
|
65
|
+
checkSourceNames(sources, {
|
|
66
|
+
resources: registry.entries.map((entry) => entry.name),
|
|
67
|
+
pages: pageModels.map((entry) => entry.name)
|
|
68
|
+
});
|
|
63
69
|
const files = createFilesRuntime({
|
|
64
70
|
...filesOptions && { options: filesOptions },
|
|
65
71
|
env: globalThis.process?.env ?? {},
|
|
@@ -88,6 +94,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
88
94
|
...assistant && { assistant: assistant.kind === "external" ? assistant.url : `${systemPath}/assistant` },
|
|
89
95
|
...runtime && { runtime },
|
|
90
96
|
plugins,
|
|
97
|
+
sources,
|
|
91
98
|
...files && { files },
|
|
92
99
|
...registry.entries.some((entry) => entry.model.events) && { actorSetting: `${changesSchema(events)}.actor` }
|
|
93
100
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { CleanupEntry, CleanupSchedule, ScheduledDelete, storageCleanupSchedule
|
|
|
18
18
|
import { CleanupReport } from "./files/cleanup-runner.js";
|
|
19
19
|
import { PluginOption, PluginRef, pluginOptions, pluginsFor, pluginsVariable } from "./plugins/plugin-settings.js";
|
|
20
20
|
import { ResourceSource, configExports } from "./resource-source.js";
|
|
21
|
+
import { SourceOption, SourceRef, checkSourceNames, sourceOptions, sourcesFor, sourcesVariable } from "./sources/source-settings.js";
|
|
21
22
|
import { RecordKeyInput, ToolRecord, ToolRecords } from "./assistant/records.js";
|
|
22
23
|
import { FunctionRecords, ListParams, RecordPage } from "./functions/records.js";
|
|
23
24
|
import { ApiFunction, CorsOptions, FunctionApp, FunctionCaller, FunctionContext, FunctionHandler, FunctionOptions, FunctionSource, ProtectedFunctionOptions, PublicFunctionContext, PublicFunctionHandler, PublicFunctionOptions, defineFunction } from "./functions/define-function.js";
|
|
@@ -35,6 +36,7 @@ import { ProjectConfig, defineConfig, mergeConfig } from "./project-config.js";
|
|
|
35
36
|
import { consoleAuditQueue } from "./audit/console-queue.js";
|
|
36
37
|
import { JwtOptions, jwtAuthenticator } from "./auth/jwt.js";
|
|
37
38
|
import { HttpProblem, badRequest, forbidden, notFound, unauthorized } from "./problem.js";
|
|
39
|
+
import { TrustedApp, withTrustedApps } from "./better-auth/app-tokens.js";
|
|
38
40
|
import { BetterAuthAuthenticatorOptions, betterAuthAuthenticator } from "./better-auth/authenticator.js";
|
|
39
41
|
import { parseRoles } from "./better-auth/parse-roles.js";
|
|
40
42
|
import { LinkedAccount, NewUser, StoredUser, createUser, deleteUser, hasUsers, listUsers, roleChoices, setUserBanned, setUserRole } from "./better-auth/users.js";
|
|
@@ -69,4 +71,4 @@ import { FatalJobError } from "./jobs/run-job.js";
|
|
|
69
71
|
import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/runtime.js";
|
|
70
72
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
71
73
|
import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
|
|
72
|
-
export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticatorOptions, type CapturedRow, type ChatMessage, type ChatTextPart, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EnqueueOptions, type EventData, type EventsOptions, FatalJobError, type FileCleanupInput, type FileObject, type FileProvider, type FileStore, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSource, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type LinkedAccount, type ListParams, type LocalFilesOptions, type MemberInput, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReasoningDetail, type ReasoningEffort, type RecordKeyInput, type RecordPage, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type ScanGuardMode, type ScheduledDelete, type Session, type SessionOrganization, type SignInPolicy, type SignInRule, type SignatureOptions, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TurnOptions, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineFunction, defineJob, defineTool, deleteUser, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, functionApp, hasUsers, imageSize, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, listOrganizations, listUsers, localFileStore, localFiles, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, startJobs, storageCleanupSchedule, streamChatCompletion, subscription, unauthorized, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints };
|
|
74
|
+
export { type Admin, type AdminAuth, type AdminOptions, type ApiFunction, type ApprovalAnswer, type ApprovalRequest, type AssistantBackend, type AssistantOptions, type AssistantSettings, type AssistantTool, type AuditEvent, type AuditQueue, type Authenticator, type BetterAuthAuthenticatorOptions, type CapturedRow, type ChatMessage, type ChatTextPart, type CleanupEntry, type CleanupReport, type CleanupSchedule, type CoalescedChange, type Completion, type CompletionOptions, type Conversation, type ConversationRecord, type ConversationStore, type ConversationsOptions, type CorsOptions, type CorsOrigins, type CreateAdminInput, type CreateAuthOptions, type DrainResult, type EnqueueOptions, type EventData, type EventsOptions, FatalJobError, type FileCleanupInput, type FileObject, type FileProvider, type FileStore, type FilesOptions, type FunctionApp, type FunctionCaller, type FunctionContext, type FunctionEnv, type FunctionHandler, type FunctionOptions, type FunctionRecords, type FunctionSource, HttpProblem, type IssueTokenOptions, type JobContext, type JobDefinition, type JobOptions, type JobsRuntime, type JwtOptions, type LinkedAccount, type ListParams, type LocalFilesOptions, type MemberInput, ModelError, type ModelSettings, type NewOrganization, type NewUser, type OperatorProvider, type OrganizationRole, type OrganizationsOptions, type PipelineHook, type PlatformSignIn, type PluginOption, type PluginRef, type ProjectConfig, type ProtectedFunctionOptions, type PublicFunctionContext, type PublicFunctionEnv, type PublicFunctionHandler, type PublicFunctionOptions, type QueryOptions, type QueryResult, type ReasoningDetail, type ReasoningEffort, type RecordKeyInput, type RecordPage, type ResourceChange, type ResourceSource, type RoleDefinition, type RolesInput, type RunContext, type RuntimeOptions, type ScanGuardMode, type ScheduledDelete, type Session, type SessionOrganization, type SignInPolicy, type SignInRule, type SignatureOptions, type SourceOption, type SourceRef, type StaffAccessRule, type StaffSignIn, type StandardSchema, type StandardWebhookOptions, type StartJobsOptions, type StoredMember, type StoredObject, type StoredOrganization, type StoredUser, type SubscriptionDefinition, type SubscriptionEvent, type SubscriptionOptions, type TenantValue, type ToolCall, type ToolContext, type ToolRecord, type ToolRecords, type ToolSpec, type TrustedApp, type TurnOptions, type User, type UserCreatedHook, type ViewSource, type WebhooksOptions, type WidgetOptions, type WriteEvent, type WriteOperation, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkSourceNames, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineFunction, defineJob, defineTool, deleteUser, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, functionApp, hasUsers, imageSize, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, listOrganizations, listUsers, localFileStore, localFiles, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startJobs, storageCleanupSchedule, streamChatCompletion, subscription, unauthorized, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
package/dist/index.js
CHANGED
|
@@ -33,10 +33,12 @@ import { storageCleanupSchedule } from "./files/cleanup-schedule.js";
|
|
|
33
33
|
import { localFileStore, localFiles } from "./files/local-files.js";
|
|
34
34
|
import { pluginOptions, pluginsFor, pluginsVariable } from "./plugins/plugin-settings.js";
|
|
35
35
|
import { runtimeUrl, runtimeVariable, seesRuntime } from "./runtime/runtime-settings.js";
|
|
36
|
+
import { checkSourceNames, sourceOptions, sourcesFor, sourcesVariable } from "./sources/source-settings.js";
|
|
36
37
|
import { createAdmin } from "./create-admin.js";
|
|
37
38
|
import { configExports } from "./resource-source.js";
|
|
38
39
|
import { defineConfig, mergeConfig } from "./project-config.js";
|
|
39
40
|
import { jwtAuthenticator } from "./auth/jwt.js";
|
|
41
|
+
import { withTrustedApps } from "./better-auth/app-tokens.js";
|
|
40
42
|
import { betterAuthAuthenticator } from "./better-auth/authenticator.js";
|
|
41
43
|
import { issueToken, maxTokenTtlSeconds } from "./better-auth/tokens.js";
|
|
42
44
|
import { authSchemaMigration, checkAuthSchema } from "./better-auth/auth-schema.js";
|
|
@@ -52,4 +54,4 @@ import { createFileCleanup } from "./files/file-cleanup.js";
|
|
|
52
54
|
import { aspectRatio, fileSize, fileType, imageSize, processor } from "./files/processors.js";
|
|
53
55
|
import { startJobs } from "./jobs/runtime.js";
|
|
54
56
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
55
|
-
export { FatalJobError, HttpProblem, ModelError, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineFunction, defineJob, defineTool, deleteUser, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, functionApp, hasUsers, imageSize, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, listOrganizations, listUsers, localFileStore, localFiles, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, startJobs, storageCleanupSchedule, streamChatCompletion, subscription, unauthorized, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints };
|
|
57
|
+
export { FatalJobError, HttpProblem, ModelError, addAppRoleToOwners, addMember, aspectRatio, assistantProtocolRoutes, assistantRoles, assistantSettings, assistantVariables, authSchemaMigration, badRequest, betterAuthAuthenticator, changesMigration, checkAuthSchema, checkChanges, checkJobsSchema, checkSourceNames, checkWebhooksSchema, coalesce, configExports, consoleAuditQueue, conversationKey, createAdmin, createAuth, createConversations, createFileCleanup, createOrganization, createUser, defaultSignInPolicy, defineConfig, defineFunction, defineJob, defineTool, deleteUser, eventMatcher, fileConversationStore, fileSize, fileType, forbidden, functionApp, hasUsers, imageSize, isJobDefinition, issueToken, jobDefinitions, jobsDrainApp, jobsSchema, jobsSchemaMigration, jwtAuthenticator, listOrganizations, listUsers, localFileStore, localFiles, maxTokenTtlSeconds, maxWidgetPropsBytes, mergeConfig, migrationCommand, newWebhookSecret, normalizeRoles, notFound, parseColumns, parseRoles, pluginOptions, pluginsFor, pluginsVariable, processor, readJson, readOnlyQueryTool, readOperatorSettings, readOutbox, readPlatformSignIn, readWriteQueryTool, recordOf, removeMember, requestApproval, requireEnv, resourceChange, roleChoices, runReadOnlyQuery, runReadWriteQuery, runTurn, runtimeUrl, runtimeVariable, seesAssistant, seesRuntime, setMemberRoles, setUserBanned, setUserRole, signInPolicyMethods, sourceOptions, sourcesFor, sourcesVariable, startJobs, storageCleanupSchedule, streamChatCompletion, subscription, unauthorized, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
package/dist/meta.js
CHANGED
|
@@ -4,6 +4,7 @@ import { requestAccess } from "./request-access.js";
|
|
|
4
4
|
import { activeRole } from "./active-role.js";
|
|
5
5
|
import { pluginsFor } from "./plugins/plugin-settings.js";
|
|
6
6
|
import { seesRuntime } from "./runtime/runtime-settings.js";
|
|
7
|
+
import { sourcesFor } from "./sources/source-settings.js";
|
|
7
8
|
import { pruneView } from "./view-access.js";
|
|
8
9
|
import { prunePage } from "@protobase/layout";
|
|
9
10
|
import { bottomBarFor, pickView } from "@protobase/schema";
|
|
@@ -37,6 +38,7 @@ const createMeta = (deps) => {
|
|
|
37
38
|
const bar = bottomBarFor(deps.bottomBars, activeRole(session));
|
|
38
39
|
const barItems = bar?.items.filter(shown) ?? [];
|
|
39
40
|
const plugins = pluginsFor(deps.plugins, session.user.roles);
|
|
41
|
+
const sources = sourcesFor(deps.sources, session.user.roles);
|
|
40
42
|
const body = {
|
|
41
43
|
resources: visible.map((access) => access.readable),
|
|
42
44
|
views,
|
|
@@ -59,7 +61,8 @@ const createMeta = (deps) => {
|
|
|
59
61
|
} },
|
|
60
62
|
...deps.assistant && seesAssistant(session.user.roles) && { assistant: { url: deps.assistant } },
|
|
61
63
|
...deps.runtime && seesRuntime(session.user.roles) && { runtime: { url: deps.runtime } },
|
|
62
|
-
...plugins.length > 0 && { plugins }
|
|
64
|
+
...plugins.length > 0 && { plugins },
|
|
65
|
+
...sources.length > 0 && { sources }
|
|
63
66
|
};
|
|
64
67
|
return {
|
|
65
68
|
body,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { checkedUrl } from "../
|
|
1
|
+
import { checkedUrl } from "../checked-url.js";
|
|
2
2
|
//#region src/runtime/runtime-settings.ts
|
|
3
3
|
const runtimeVariable = "PROTOBASE_RUNTIME_URL";
|
|
4
4
|
/** Only an admin sees the runtime endpoint: `/meta` names it to nobody else. */
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
//#region src/sources/source-settings.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Another Protobase API whose resources the admin app draws as its own: `url` is its resources API (`.../api/v1`),
|
|
4
|
+
* `label` names it to people when it cannot be reached, and `resources` are the ones the app draws from it, at their
|
|
5
|
+
* own addresses. With `roles`, `/meta` names it only to callers holding one of them; without, to everyone.
|
|
6
|
+
*/
|
|
7
|
+
export type SourceOption = {
|
|
8
|
+
url: string;
|
|
9
|
+
label: string;
|
|
10
|
+
resources: string[];
|
|
11
|
+
roles?: string[];
|
|
12
|
+
};
|
|
13
|
+
/** A source as `/meta` names it to a caller. */
|
|
14
|
+
export type SourceRef = {
|
|
15
|
+
url: string;
|
|
16
|
+
label: string;
|
|
17
|
+
resources: string[];
|
|
18
|
+
};
|
|
19
|
+
/** The platform's sources, as a JSON list of `SourceOption`, drawn after the ones `options.sources` lists. */
|
|
20
|
+
export declare const sourcesVariable = "PROTOBASE_SOURCES";
|
|
21
|
+
/**
|
|
22
|
+
* The sources the app draws resources from: those `options.sources` lists, then those `PROTOBASE_SOURCES` adds; `false`
|
|
23
|
+
* turns both off. A source that cannot work, one listed twice, or a resource two sources draw stops the server at
|
|
24
|
+
* startup.
|
|
25
|
+
*/
|
|
26
|
+
export declare const sourceOptions: (option: SourceOption[] | false | undefined, env: Record<string, string | undefined>) => SourceOption[];
|
|
27
|
+
/**
|
|
28
|
+
* Fails at startup when a source draws a resource under a name the app already uses, for one of its own resources or
|
|
29
|
+
* a composed page: the address `/<name>` would mean two things.
|
|
30
|
+
*/
|
|
31
|
+
export declare const checkSourceNames: (sources: readonly SourceOption[], own: {
|
|
32
|
+
resources: readonly string[];
|
|
33
|
+
pages: readonly string[];
|
|
34
|
+
}) => void;
|
|
35
|
+
/** The sources `/meta` names to a caller with `roles`: those for everyone, and those for one of their roles. */
|
|
36
|
+
export declare const sourcesFor: (sources: readonly SourceOption[], roles: readonly string[]) => SourceRef[];
|
|
37
|
+
//#endregion
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { checkedUrl } from "../checked-url.js";
|
|
2
|
+
//#region src/sources/source-settings.ts
|
|
3
|
+
/** The platform's sources, as a JSON list of `SourceOption`, drawn after the ones `options.sources` lists. */
|
|
4
|
+
const sourcesVariable = "PROTOBASE_SOURCES";
|
|
5
|
+
const sourceKeys = /* @__PURE__ */ new Set([
|
|
6
|
+
"url",
|
|
7
|
+
"label",
|
|
8
|
+
"resources",
|
|
9
|
+
"roles"
|
|
10
|
+
]);
|
|
11
|
+
const isStringList = (value) => Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
12
|
+
const resourceNameProblem = (name) => name === "" ? "is empty" : name === "-" ? "is the prefix of the app's own pages" : name.includes("/") ? "holds a slash" : void 0;
|
|
13
|
+
const checkedSource = (value, where) => {
|
|
14
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${where} is not an object such as { "url": "https://...", "label": "...", "resources": ["..."] }`);
|
|
15
|
+
const unknown = Object.keys(value).filter((key) => !sourceKeys.has(key));
|
|
16
|
+
if (unknown.length > 0) throw new Error(`${where} has ${unknown.map((key) => `"${key}"`).join(", ")}, which a source does not take; it takes url, label, resources and roles`);
|
|
17
|
+
const { url, label, resources, roles } = value;
|
|
18
|
+
if (typeof url !== "string") throw new Error(`${where} needs a url, the other API's resources API such as https://other.example.com/api/v1`);
|
|
19
|
+
if (typeof label !== "string" || label.trim() === "") throw new Error(`${where} needs a label, its name as people read it`);
|
|
20
|
+
if (!isStringList(resources) || resources.length === 0) throw new Error(`${where} needs resources, a list of the resource names the app draws from it`);
|
|
21
|
+
for (const name of resources) {
|
|
22
|
+
const problem = resourceNameProblem(name);
|
|
23
|
+
if (problem) throw new Error(`${where} lists the resource "${name}", which ${problem}`);
|
|
24
|
+
}
|
|
25
|
+
if (roles !== void 0 && !isStringList(roles)) throw new Error(`${where} has roles that are not a list of role names`);
|
|
26
|
+
const checked = checkedUrl(url.trim(), `${where}'s url`);
|
|
27
|
+
const { pathname, search, hash } = new URL(checked);
|
|
28
|
+
if (pathname === "/" || search || hash) throw new Error(`${where}'s url is "${checked}"; it is the other API's resources API, a path and nothing after it, such as https://other.example.com/api/v1`);
|
|
29
|
+
return {
|
|
30
|
+
url: checked,
|
|
31
|
+
label: label.trim(),
|
|
32
|
+
resources,
|
|
33
|
+
...roles && { roles }
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
const listedSources = (env) => {
|
|
37
|
+
const text = env[sourcesVariable]?.trim();
|
|
38
|
+
if (!text) return [];
|
|
39
|
+
const parse = () => {
|
|
40
|
+
try {
|
|
41
|
+
return JSON.parse(text);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof SyntaxError) throw new Error(`${sourcesVariable} is not JSON; it is a list such as [{ "url": "https://...", "label": "...", "resources": ["..."] }]`);
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const parsed = parse();
|
|
48
|
+
if (!Array.isArray(parsed)) throw new Error(`${sourcesVariable} is not a list; it is a list such as [{ "url": "https://...", "label": "...", "resources": ["..."] }]`);
|
|
49
|
+
return parsed.map((value, index) => checkedSource(value, `${sourcesVariable}[${index}]`));
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* The sources the app draws resources from: those `options.sources` lists, then those `PROTOBASE_SOURCES` adds; `false`
|
|
53
|
+
* turns both off. A source that cannot work, one listed twice, or a resource two sources draw stops the server at
|
|
54
|
+
* startup.
|
|
55
|
+
*/
|
|
56
|
+
const sourceOptions = (option, env) => {
|
|
57
|
+
if (option === false) return [];
|
|
58
|
+
const all = [...(option ?? []).map((value, index) => checkedSource(value, `options.sources[${index}]`)), ...listedSources(env)];
|
|
59
|
+
const urls = /* @__PURE__ */ new Set();
|
|
60
|
+
const drawn = /* @__PURE__ */ new Map();
|
|
61
|
+
for (const source of all) {
|
|
62
|
+
if (urls.has(source.url)) throw new Error(`The source ${source.url} is listed twice; list it once, with every resource the app draws from it`);
|
|
63
|
+
urls.add(source.url);
|
|
64
|
+
for (const name of source.resources) {
|
|
65
|
+
const other = drawn.get(name);
|
|
66
|
+
if (other !== void 0) throw new Error(`The resource "${name}" is drawn from ${other === source.url ? `${other} twice` : `both ${other} and ${source.url}`}; draw it from one source, once`);
|
|
67
|
+
drawn.set(name, source.url);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return all;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Fails at startup when a source draws a resource under a name the app already uses, for one of its own resources or
|
|
74
|
+
* a composed page: the address `/<name>` would mean two things.
|
|
75
|
+
*/
|
|
76
|
+
const checkSourceNames = (sources, own) => {
|
|
77
|
+
for (const source of sources) for (const name of source.resources) {
|
|
78
|
+
const clash = own.resources.includes(name) ? "resource" : own.pages.includes(name) ? "page" : void 0;
|
|
79
|
+
if (clash) throw new Error(`The source ${source.label} (${source.url}) draws the resource "${name}", which is also a ${clash} of the app's own; rename one of them, or leave it out of the source's resources`);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
/** The sources `/meta` names to a caller with `roles`: those for everyone, and those for one of their roles. */
|
|
83
|
+
const sourcesFor = (sources, roles) => sources.filter((source) => !source.roles || source.roles.some((role) => roles.includes(role))).map(({ url, label, resources }) => ({
|
|
84
|
+
url,
|
|
85
|
+
label,
|
|
86
|
+
resources
|
|
87
|
+
}));
|
|
88
|
+
//#endregion
|
|
89
|
+
export { checkSourceNames, sourceOptions, sourcesFor, sourcesVariable };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.3",
|
|
4
4
|
"description": "The Protobase REST API as a Hono app, with Better Auth sign-in.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -30,9 +30,9 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@better-auth/passkey": "^1.7.7",
|
|
33
|
-
"@protobase/layout": "0.12.
|
|
34
|
-
"@protobase/query": "0.12.
|
|
35
|
-
"@protobase/schema": "0.12.
|
|
33
|
+
"@protobase/layout": "0.12.3",
|
|
34
|
+
"@protobase/query": "0.12.3",
|
|
35
|
+
"@protobase/schema": "0.12.3",
|
|
36
36
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
37
37
|
"@simplewebauthn/server": "^13.3.1",
|
|
38
38
|
"better-auth": "^1.7.7",
|