@protobase/server 0.12.7 → 0.12.8
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 +9 -3
- package/dist/anonymous.js +27 -0
- package/dist/auth/jwt.d.ts +1 -1
- package/dist/auth/jwt.js +5 -3
- package/dist/check-roles.js +29 -0
- package/dist/create-admin.js +6 -3
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -2
- package/dist/problem.d.ts +8 -0
- package/dist/problem.js +11 -1
- package/dist/request-access.js +6 -2
- package/dist/types.d.ts +2 -0
- package/package.json +4 -4
package/dist/admin-options.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import { SourceOption } from "./sources/source-settings.js";
|
|
|
5
5
|
import { CorsOrigins } from "./api-cors.js";
|
|
6
6
|
import { AssistantOptions } from "./assistant/assistant-settings.js";
|
|
7
7
|
import { RuntimeOptions } from "./runtime/runtime-settings.js";
|
|
8
|
-
import { ResolveOptions } from "@protobase/schema";
|
|
8
|
+
import { ResolveOptions, RoleLimit } from "@protobase/schema";
|
|
9
9
|
//#region src/admin-options.d.ts
|
|
10
10
|
export type AdminOptions = {
|
|
11
11
|
/** Default `/api/v1`. */
|
|
@@ -19,8 +19,14 @@ export type AdminOptions = {
|
|
|
19
19
|
};
|
|
20
20
|
/** Runs inside the write transaction after each create, update and delete. */
|
|
21
21
|
writeHooks?: PipelineHook[];
|
|
22
|
-
/**
|
|
23
|
-
|
|
22
|
+
/**
|
|
23
|
+
* The project's roles (from `defineRoles`): an operation without an access rule needs the matching capability. Their
|
|
24
|
+
* limits are checked against the resources at startup; an `anonymous` role opens the resources API to requests
|
|
25
|
+
* without a token.
|
|
26
|
+
*/
|
|
27
|
+
roles?: ResolveOptions['roles'] & {
|
|
28
|
+
limits?: readonly RoleLimit[];
|
|
29
|
+
};
|
|
24
30
|
/** What an operation without a rule does; overrides the capability check. Without `roles` the default is allow. */
|
|
25
31
|
defaultAccess?: ResolveOptions['defaultAccess'];
|
|
26
32
|
/** Whether list items carry their `permissions` (update, delete): true by default, or `{ except: [...] }` for resources that opt out. */
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { MissingCredentials } from "./problem.js";
|
|
2
|
+
import { anonymousRole } from "@protobase/schema";
|
|
3
|
+
//#region src/anonymous.ts
|
|
4
|
+
const anonymousSession = () => ({
|
|
5
|
+
user: {
|
|
6
|
+
id: "",
|
|
7
|
+
roles: [anonymousRole]
|
|
8
|
+
},
|
|
9
|
+
anonymous: true
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* With an `anonymous` role in `roles`, a request that carries no credential at all (`MissingCredentials`) is served as
|
|
13
|
+
* a caller holding that role alone; an invalid or expired one is still a 401. Without the role, `authenticate` itself.
|
|
14
|
+
*/
|
|
15
|
+
const withAnonymous = (authenticate, roles) => {
|
|
16
|
+
if (!Object.hasOwn(roles?.definitions ?? {}, anonymousRole)) return authenticate;
|
|
17
|
+
return async (request) => {
|
|
18
|
+
try {
|
|
19
|
+
return await authenticate(request);
|
|
20
|
+
} catch (error) {
|
|
21
|
+
if (error instanceof MissingCredentials) return anonymousSession();
|
|
22
|
+
throw error;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
//#endregion
|
|
27
|
+
export { withAnonymous };
|
package/dist/auth/jwt.d.ts
CHANGED
|
@@ -20,6 +20,6 @@ export type JwtOptions = {
|
|
|
20
20
|
/** A fixed tenant (or resolver) used instead of any claim, for single-tenant deployments. */
|
|
21
21
|
tenant?: TenantValue | (() => Promise<TenantValue | undefined>);
|
|
22
22
|
};
|
|
23
|
-
/** Verifies `Authorization: Bearer <jwt>` against a JWKS; `sub` is the user id. */
|
|
23
|
+
/** Verifies `Authorization: Bearer <jwt>` against a JWKS; `sub` is the user id. Without the header, `MissingCredentials`. */
|
|
24
24
|
export declare const jwtAuthenticator: (options: JwtOptions) => Authenticator;
|
|
25
25
|
//#endregion
|
package/dist/auth/jwt.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { unauthorized } from "../problem.js";
|
|
1
|
+
import { MissingCredentials, unauthorized } from "../problem.js";
|
|
2
2
|
import { createRemoteJWKSet, errors, jwtVerify } from "jose";
|
|
3
3
|
//#region src/auth/jwt.ts
|
|
4
4
|
const asymmetric = [
|
|
@@ -17,13 +17,15 @@ const rolesOf = (claim) => {
|
|
|
17
17
|
if (typeof claim === "string") return claim.split(/[\s,]+/).filter(Boolean);
|
|
18
18
|
return Array.isArray(claim) ? claim.filter((role) => typeof role === "string") : [];
|
|
19
19
|
};
|
|
20
|
-
/** Verifies `Authorization: Bearer <jwt>` against a JWKS; `sub` is the user id. */
|
|
20
|
+
/** Verifies `Authorization: Bearer <jwt>` against a JWKS; `sub` is the user id. Without the header, `MissingCredentials`. */
|
|
21
21
|
const jwtAuthenticator = (options) => {
|
|
22
22
|
const { rolesClaim = "roles", tenantClaim = "tenant" } = options;
|
|
23
23
|
const keys = options.keys ?? (options.jwksUrl ? createRemoteJWKSet(new URL(options.jwksUrl)) : void 0);
|
|
24
24
|
if (!keys) throw new Error("jwtAuthenticator needs jwksUrl or keys");
|
|
25
25
|
return async (request) => {
|
|
26
|
-
const
|
|
26
|
+
const header = request.headers.get("authorization");
|
|
27
|
+
if (header === null) throw new MissingCredentials("Send a bearer token in the Authorization header");
|
|
28
|
+
const [scheme, token] = header.split(" ");
|
|
27
29
|
if (scheme?.toLowerCase() !== "bearer" || !token) throw unauthorized("Send a bearer token in the Authorization header");
|
|
28
30
|
const verified = await jwtVerify(token, keys, {
|
|
29
31
|
algorithms: options.algorithms ?? asymmetric,
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { anonymousRole, checkFilter } from "@protobase/schema";
|
|
2
|
+
//#region src/check-roles.ts
|
|
3
|
+
/**
|
|
4
|
+
* Stops startup on a role's limit that does not fit its resource: fields or a filter for a name that is not a resource,
|
|
5
|
+
* a field it does not have, a filter it cannot check, or an anonymous capability on a tenant-scoped resource, since an
|
|
6
|
+
* anonymous caller works in no organization.
|
|
7
|
+
*/
|
|
8
|
+
const checkRoleLimits = (registry, limits) => {
|
|
9
|
+
for (const { role, capability, fields, filter } of limits) {
|
|
10
|
+
const name = capability.slice(0, capability.indexOf("."));
|
|
11
|
+
const model = registry.find(name)?.model;
|
|
12
|
+
if (!model) throw new Error(`Role "${role}" limits "${capability}" but "${name}" is not a resource`);
|
|
13
|
+
const unknown = fields?.find((field) => !Object.hasOwn(model.fields, field));
|
|
14
|
+
if (unknown) throw new Error(`Role "${role}" lists the field "${unknown}" for "${capability}" but ${name} has no such field`);
|
|
15
|
+
if (role === anonymousRole && model.tenant) throw new Error(`The anonymous role reads ${name}, which is tenant-scoped, but an anonymous caller works in no organization`);
|
|
16
|
+
if (!filter) continue;
|
|
17
|
+
const trusted = {
|
|
18
|
+
...model,
|
|
19
|
+
fields: Object.fromEntries(Object.entries(model.fields).map(([field, value]) => [field, {
|
|
20
|
+
...value,
|
|
21
|
+
filterable: true
|
|
22
|
+
}]))
|
|
23
|
+
};
|
|
24
|
+
const checked = checkFilter(trusted, filter);
|
|
25
|
+
if (!checked.ok) throw new Error(`Role "${role}" limits "${capability}" to a filter that does not fit ${name}: ${checked.errors[0].message}`);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { checkRoleLimits };
|
package/dist/create-admin.js
CHANGED
|
@@ -14,7 +14,9 @@ import { toApiFunction } from "./functions/define-function.js";
|
|
|
14
14
|
import { createWebhooks } from "./webhooks/webhooks.js";
|
|
15
15
|
import { webhookTable } from "./webhooks/table/webhook-table.js";
|
|
16
16
|
import { subscriptionWriteCheck } from "./webhooks/table/validate-hook.js";
|
|
17
|
+
import { withAnonymous } from "./anonymous.js";
|
|
17
18
|
import { checkBottomBars, checkShell } from "./check-shell.js";
|
|
19
|
+
import { checkRoleLimits } from "./check-roles.js";
|
|
18
20
|
import { checkViews } from "./check-views.js";
|
|
19
21
|
import { errorReportingSettings } from "./error-reporting/error-reporting-settings.js";
|
|
20
22
|
import { createFilesRuntime } from "./files/runtime.js";
|
|
@@ -54,6 +56,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
54
56
|
const bottomBarModels = bottomBars.map((bar) => bar.toBottomBarModel());
|
|
55
57
|
checkBottomBars(registry, bottomBarModels, pageModels.map((entry) => entry.name), declaredRoles(auth, options.roles));
|
|
56
58
|
checkViews(registry, viewModels, pageModels.map((entry) => entry.name), fieldTypes);
|
|
59
|
+
checkRoleLimits(registry, options.roles?.limits ?? []);
|
|
57
60
|
checkPages(pageModels, {
|
|
58
61
|
models: Object.fromEntries(registry.entries.map((entry) => [entry.name, entry.model])),
|
|
59
62
|
actions: (resource) => new Set(viewModels.filter((view) => view.resource === resource).flatMap((view) => view.actions.map((action) => action.name)))
|
|
@@ -103,7 +106,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
103
106
|
};
|
|
104
107
|
const meta = createMeta(deps);
|
|
105
108
|
const onError = createErrorHandler(options.onUnhandledError);
|
|
106
|
-
const guarded = (routes) => {
|
|
109
|
+
const guarded = (routes, caller = authenticate) => {
|
|
107
110
|
const app = new Hono();
|
|
108
111
|
app.onError(onError);
|
|
109
112
|
app.use("*", async (c, next) => {
|
|
@@ -111,7 +114,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
111
114
|
c.res.headers.set("X-Meta-Version", await meta.version(c.get("session")?.user.roles ?? []));
|
|
112
115
|
});
|
|
113
116
|
app.use("*", async (c, next) => {
|
|
114
|
-
c.set("session", await
|
|
117
|
+
c.set("session", await caller(c.req.raw));
|
|
115
118
|
await next();
|
|
116
119
|
});
|
|
117
120
|
routes(app);
|
|
@@ -120,7 +123,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
120
123
|
const api = guarded((app) => {
|
|
121
124
|
app.route("/", collectionRoutes(deps));
|
|
122
125
|
app.route("/", recordRoutes(deps));
|
|
123
|
-
});
|
|
126
|
+
}, withAnonymous(authenticate, options.roles));
|
|
124
127
|
const system = guarded((app) => {
|
|
125
128
|
app.route("/", docsRoutes(deps));
|
|
126
129
|
app.route("/", metaRoutes(meta));
|
package/dist/index.d.ts
CHANGED
|
@@ -36,7 +36,7 @@ import { CreateAdminInput, createAdmin } from "./create-admin.js";
|
|
|
36
36
|
import { ProjectConfig, defineConfig, mergeConfig } from "./project-config.js";
|
|
37
37
|
import { consoleAuditQueue } from "./audit/console-queue.js";
|
|
38
38
|
import { JwtOptions, jwtAuthenticator } from "./auth/jwt.js";
|
|
39
|
-
import { HttpProblem, badRequest, forbidden, notFound, unauthorized } from "./problem.js";
|
|
39
|
+
import { HttpProblem, MissingCredentials, badRequest, forbidden, notFound, unauthorized } from "./problem.js";
|
|
40
40
|
import { TrustedApp, withTrustedApps } from "./better-auth/app-tokens.js";
|
|
41
41
|
import { BetterAuthAuthenticatorOptions, betterAuthAuthenticator } from "./better-auth/authenticator.js";
|
|
42
42
|
import { parseRoles } from "./better-auth/parse-roles.js";
|
|
@@ -74,4 +74,4 @@ import { FatalJobError } from "./jobs/run-job.js";
|
|
|
74
74
|
import { DrainResult, JobsRuntime, StartJobsOptions, startJobs } from "./jobs/runtime.js";
|
|
75
75
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
76
76
|
import { checkJobsSchema, jobsSchema, jobsSchemaMigration } from "./jobs/schema.js";
|
|
77
|
-
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 ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, 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, errorReportingSettings, errorReportingVariable, 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, startErrorReporting, startJobs, storageCleanupSchedule, streamChatCompletion, subscription, unauthorized, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
|
77
|
+
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 ErrorReporter, type ErrorReportingOptions, type ErrorReportingSettings, 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, MissingCredentials, 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, errorReportingSettings, errorReportingVariable, 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, startErrorReporting, startJobs, storageCleanupSchedule, streamChatCompletion, subscription, unauthorized, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assistantRoles, assistantSettings, assistantVariables, seesAssistant } from "./assistant/assistant-settings.js";
|
|
2
2
|
import { conversationKey, createConversations } from "./assistant/conversations.js";
|
|
3
3
|
import { fileConversationStore } from "./assistant/file-conversation-store.js";
|
|
4
|
-
import { HttpProblem, badRequest, forbidden, notFound, unauthorized } from "./problem.js";
|
|
4
|
+
import { HttpProblem, MissingCredentials, badRequest, forbidden, notFound, unauthorized } from "./problem.js";
|
|
5
5
|
import { assistantProtocolRoutes } from "./assistant/protocol-routes.js";
|
|
6
6
|
import { runReadOnlyQuery, runReadWriteQuery } from "./assistant/run-query.js";
|
|
7
7
|
import { defineTool } from "./assistant/tools.js";
|
|
@@ -56,4 +56,4 @@ import { createFileCleanup } from "./files/file-cleanup.js";
|
|
|
56
56
|
import { aspectRatio, fileSize, fileType, imageSize, processor } from "./files/processors.js";
|
|
57
57
|
import { startJobs } from "./jobs/runtime.js";
|
|
58
58
|
import { jobsDrainApp } from "./jobs/drain-route.js";
|
|
59
|
-
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, errorReportingSettings, errorReportingVariable, 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, startErrorReporting, startJobs, storageCleanupSchedule, streamChatCompletion, subscription, unauthorized, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
|
59
|
+
export { FatalJobError, HttpProblem, MissingCredentials, 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, errorReportingSettings, errorReportingVariable, 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, startErrorReporting, startJobs, storageCleanupSchedule, streamChatCompletion, subscription, unauthorized, verifySignature, verifyStandardWebhook, webauthnPath, webhooks, webhooksMigration, widget, withCacheBreakpoints, withTrustedApps };
|
package/dist/problem.d.ts
CHANGED
|
@@ -12,6 +12,14 @@ export declare class HttpProblem extends Error {
|
|
|
12
12
|
}
|
|
13
13
|
export declare const badRequest: (slug: string, detail: string, extras?: ProblemExtras) => HttpProblem;
|
|
14
14
|
export declare const unauthorized: (detail: string) => HttpProblem;
|
|
15
|
+
/**
|
|
16
|
+
* The 401 of a request that carries no credential at all, as opposed to an invalid one: with an `anonymous` role, the
|
|
17
|
+
* resources API serves such a request as an anonymous caller instead. An authenticator of the app's own throws it to
|
|
18
|
+
* let that happen.
|
|
19
|
+
*/
|
|
20
|
+
export declare class MissingCredentials extends HttpProblem {
|
|
21
|
+
constructor(detail: string);
|
|
22
|
+
}
|
|
15
23
|
export declare const forbidden: (slug: string, detail: string) => HttpProblem;
|
|
16
24
|
export declare const notFound: (detail: string) => HttpProblem;
|
|
17
25
|
//#endregion
|
package/dist/problem.js
CHANGED
|
@@ -35,9 +35,19 @@ const problemResponse = (problem, instance) => new Response(JSON.stringify({
|
|
|
35
35
|
});
|
|
36
36
|
const badRequest = (slug, detail, extras) => new HttpProblem(400, slug, "Bad Request", detail, extras);
|
|
37
37
|
const unauthorized = (detail) => new HttpProblem(401, "unauthenticated", "Unauthorized", detail, {}, { "www-authenticate": "Bearer" });
|
|
38
|
+
/**
|
|
39
|
+
* The 401 of a request that carries no credential at all, as opposed to an invalid one: with an `anonymous` role, the
|
|
40
|
+
* resources API serves such a request as an anonymous caller instead. An authenticator of the app's own throws it to
|
|
41
|
+
* let that happen.
|
|
42
|
+
*/
|
|
43
|
+
var MissingCredentials = class extends HttpProblem {
|
|
44
|
+
constructor(detail) {
|
|
45
|
+
super(401, "unauthenticated", "Unauthorized", detail, {}, { "www-authenticate": "Bearer" });
|
|
46
|
+
}
|
|
47
|
+
};
|
|
38
48
|
const forbidden = (slug, detail) => new HttpProblem(403, slug, "Forbidden", detail);
|
|
39
49
|
const notFound = (detail) => new HttpProblem(404, "not-found", "Not Found", detail);
|
|
40
50
|
const preconditionFailed = (detail) => new HttpProblem(412, "precondition-failed", "Precondition Failed", detail);
|
|
41
51
|
const preconditionRequired = (detail) => new HttpProblem(428, "precondition-required", "Precondition Required", detail);
|
|
42
52
|
//#endregion
|
|
43
|
-
export { HttpProblem, badRequest, forbidden, notFound, preconditionFailed, preconditionRequired, problemResponse, problemType, unauthorized };
|
|
53
|
+
export { HttpProblem, MissingCredentials, badRequest, forbidden, notFound, preconditionFailed, preconditionRequired, problemResponse, problemType, unauthorized };
|
package/dist/request-access.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { forbidden, notFound } from "./problem.js";
|
|
1
|
+
import { forbidden, notFound, unauthorized } from "./problem.js";
|
|
2
2
|
import { checkedFilter } from "./filter-input.js";
|
|
3
3
|
import { tenantScope } from "./tenant.js";
|
|
4
4
|
import { checkFilter, resolveAccess, restrictModel } from "@protobase/schema";
|
|
@@ -28,10 +28,14 @@ const requestAccess = async (deps, entry, session) => {
|
|
|
28
28
|
};
|
|
29
29
|
};
|
|
30
30
|
const hasNoAccess = (resolved) => !Object.values(resolved.operations).some(Boolean);
|
|
31
|
-
/**
|
|
31
|
+
/**
|
|
32
|
+
* 403 when the operation is denied, 401 for a request without a token; a resource the user can do nothing with does not
|
|
33
|
+
* exist for them.
|
|
34
|
+
*/
|
|
32
35
|
const requireOperation = (access, operation) => {
|
|
33
36
|
if (access.resolved.operations[operation]) return;
|
|
34
37
|
if (hasNoAccess(access.resolved)) throw notFound(`There is no resource "${access.entry.name}"`);
|
|
38
|
+
if (access.session.anonymous) throw unauthorized(`Send a bearer token to ${operation} ${access.entry.name}`);
|
|
35
39
|
throw forbidden("access-denied", `You may not ${operation} ${access.entry.name}`);
|
|
36
40
|
};
|
|
37
41
|
/**
|
package/dist/types.d.ts
CHANGED
|
@@ -13,10 +13,12 @@ export type User = {
|
|
|
13
13
|
export type SessionOrganization = {
|
|
14
14
|
id: TenantValue;
|
|
15
15
|
};
|
|
16
|
+
/** `anonymous` marks a request without a token, served as the project's `anonymous` role. */
|
|
16
17
|
export type Session = {
|
|
17
18
|
user: User;
|
|
18
19
|
tenant?: TenantValue;
|
|
19
20
|
organization?: SessionOrganization;
|
|
21
|
+
anonymous?: true;
|
|
20
22
|
};
|
|
21
23
|
/** Resolves the caller of a request, or throws a 401 problem (see `unauthorized`). */
|
|
22
24
|
export type Authenticator = (request: Request) => Promise<Session> | Session;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.8",
|
|
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.8",
|
|
34
|
+
"@protobase/query": "0.12.8",
|
|
35
|
+
"@protobase/schema": "0.12.8",
|
|
36
36
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
37
37
|
"@sentry/core": "^11.7.0",
|
|
38
38
|
"@simplewebauthn/server": "^13.3.1",
|