@protobase/server 0.21.3 → 0.21.4
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/app-name.js +3 -1
- package/dist/better-auth/create-auth.d.ts +5 -11
- package/dist/better-auth/create-auth.js +3 -2
- package/dist/blocked-delete.js +38 -0
- package/dist/create-admin.js +2 -1
- package/dist/error-handler.js +1 -1
- package/dist/meta.js +2 -1
- package/dist/write-pipeline.js +6 -1
- package/package.json +4 -4
package/dist/app-name.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
//#region src/app-name.ts
|
|
2
2
|
const appNameVariable = "PROTOBASE_APP_NAME";
|
|
3
|
+
/** The app's name when neither the app nor the platform names it. */
|
|
4
|
+
const defaultAppName = "Protobase";
|
|
3
5
|
/**
|
|
4
6
|
* The app's name the platform passes in `PROTOBASE_APP_NAME`, such as `Property Finder`; `undefined` when it is not
|
|
5
7
|
* set. Protobase names the app with it in sign-in prompts and in the mail it sends, unless the app names itself.
|
|
6
8
|
*/
|
|
7
9
|
const readAppName = (env) => env["PROTOBASE_APP_NAME"]?.trim() || void 0;
|
|
8
10
|
//#endregion
|
|
9
|
-
export { appNameVariable, readAppName };
|
|
11
|
+
export { appNameVariable, defaultAppName, readAppName };
|
|
@@ -800,7 +800,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
800
800
|
expiresAt: Date;
|
|
801
801
|
token: string;
|
|
802
802
|
ipAddress?: string | null | undefined;
|
|
803
|
-
userAgent
|
|
803
|
+
userAgent? /** Open sign-up is on and offered: the app asks for it, and can mail the codes. */ : string | null | undefined;
|
|
804
804
|
};
|
|
805
805
|
};
|
|
806
806
|
}>>[];
|
|
@@ -3749,15 +3749,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
3749
3749
|
name?: string;
|
|
3750
3750
|
slug?: string;
|
|
3751
3751
|
logo?: string | null;
|
|
3752
|
-
metadata
|
|
3753
|
-
/**
|
|
3754
|
-
* The options `createAuth` gives Better Auth, with the roles, default role, mailer, operator provider and platform
|
|
3755
|
-
* sign-in provider already resolved.
|
|
3756
|
-
*/
|
|
3757
|
-
/**
|
|
3758
|
-
* The options `createAuth` gives Better Auth, with the roles, default role, mailer, operator provider and platform
|
|
3759
|
-
* sign-in provider already resolved.
|
|
3760
|
-
*/ : Record<string, any>;
|
|
3752
|
+
metadata?: Record<string, any>;
|
|
3761
3753
|
[key: string]: any;
|
|
3762
3754
|
};
|
|
3763
3755
|
}>;
|
|
@@ -4240,7 +4232,7 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
4240
4232
|
hooks: {
|
|
4241
4233
|
after: {
|
|
4242
4234
|
matcher: (context: {
|
|
4243
|
-
path
|
|
4235
|
+
path? /** Sign-in attempts allowed per minute and client, default 5; also the sign-up requests allowed. */ : string;
|
|
4244
4236
|
}) => boolean;
|
|
4245
4237
|
handler: import("better-auth").Middleware<import("better-auth").MiddlewareOptions, (inputContext: import("better-auth").MiddlewareInputContext<import("better-auth").MiddlewareOptions>) => Promise<void>>;
|
|
4246
4238
|
}[];
|
|
@@ -4374,6 +4366,8 @@ export declare const createAuth: (options: CreateAuthOptions) => import("better-
|
|
|
4374
4366
|
/** Open sign-up is on and offered: the app asks for it, and can mail the codes. */
|
|
4375
4367
|
signUp: boolean;
|
|
4376
4368
|
socialProviders: string[];
|
|
4369
|
+
/** The app's name it signs in and mails as: `appName`, else `PROTOBASE_APP_NAME`, else `Protobase`. */
|
|
4370
|
+
appName: string;
|
|
4377
4371
|
operator: string | undefined;
|
|
4378
4372
|
platformSignIn: {
|
|
4379
4373
|
provider: string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { multiHostBaseURL, readOrigins, resolveOrigins } from "./origins.js";
|
|
2
|
-
import { readAppName } from "../app-name.js";
|
|
2
|
+
import { defaultAppName, readAppName } from "../app-name.js";
|
|
3
3
|
import { platformMailer } from "../mail/platform-mailer.js";
|
|
4
4
|
import { readOperatorSettings, resolveOperator } from "./operator-provider.js";
|
|
5
5
|
import { roleDefinitions } from "./organizations/role-definitions.js";
|
|
@@ -29,7 +29,6 @@ const platformSignInSettings = () => readPlatformSignIn(globalThis.process?.env
|
|
|
29
29
|
const platformOrigins = () => readOrigins(globalThis.process?.env ?? {});
|
|
30
30
|
const platformPasskeyDomain = () => readPasskeyDomain(globalThis.process?.env ?? {});
|
|
31
31
|
const platformAppName = () => readAppName(globalThis.process?.env ?? {});
|
|
32
|
-
const defaultAppName = "Protobase";
|
|
33
32
|
const tokenLifetime = "15m";
|
|
34
33
|
const authBasePath = "/api/auth";
|
|
35
34
|
/**
|
|
@@ -277,6 +276,8 @@ const createAuth = (options) => {
|
|
|
277
276
|
/** Open sign-up is on and offered: the app asks for it, and can mail the codes. */
|
|
278
277
|
signUp: Boolean(signUpRole && mailer),
|
|
279
278
|
socialProviders,
|
|
279
|
+
/** The app's name it signs in and mails as: `appName`, else `PROTOBASE_APP_NAME`, else `Protobase`. */
|
|
280
|
+
appName: appName ?? "Protobase",
|
|
280
281
|
operator: operator?.name,
|
|
281
282
|
platformSignIn: platformSignIn && {
|
|
282
283
|
provider: platformSignIn.provider,
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { HttpProblem } from "./problem.js";
|
|
2
|
+
import { requestAccess } from "./request-access.js";
|
|
3
|
+
//#region src/blocked-delete.ts
|
|
4
|
+
/**
|
|
5
|
+
* The resource whose rows kept `deleted`'s record from being deleted, from the table a foreign key violation names, and
|
|
6
|
+
* its relation field pointing at `deleted` when there is exactly one, on a single column, so its list can be filtered by
|
|
7
|
+
* it. `undefined` when no resource or several read that table, or none of its fields points at `deleted`, as when a
|
|
8
|
+
* cascade was blocked further down.
|
|
9
|
+
*/
|
|
10
|
+
const blockingRelation = (error, deleted, entries) => {
|
|
11
|
+
const tables = entries.filter(({ model }) => model.table.name === error.table && (model.table.schema === void 0 || model.table.schema === error.schema));
|
|
12
|
+
if (tables.length !== 1) return void 0;
|
|
13
|
+
const entry = tables[0];
|
|
14
|
+
const pointing = Object.entries(entry.model.fields).filter(([, field]) => field.type === "relation" && field.relation?.resource === deleted.name);
|
|
15
|
+
if (pointing.length === 0) return void 0;
|
|
16
|
+
const only = pointing.length === 1 ? pointing[0] : void 0;
|
|
17
|
+
return only && only[1].relation.columns.length === 1 ? {
|
|
18
|
+
entry,
|
|
19
|
+
field: only[0]
|
|
20
|
+
} : { entry };
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* The 409 for a delete other records still point at. It names their resource (`blockedBy`) only to a caller who may
|
|
24
|
+
* list it, and the field pointing at the record only when they may filter by it, as with any field they cannot see.
|
|
25
|
+
*/
|
|
26
|
+
const blockedDelete = async (deps, access, error) => {
|
|
27
|
+
const match = blockingRelation(error, access.entry, deps.registry.entries);
|
|
28
|
+
const blocking = match && await requestAccess(deps, match.entry, access.session);
|
|
29
|
+
const listed = blocking && blocking.resolved.operations.list;
|
|
30
|
+
const field = listed && match.field && blocking.model.fields[match.field]?.filterable ? match.field : void 0;
|
|
31
|
+
const blockedBy = listed ? {
|
|
32
|
+
resource: match.entry.name,
|
|
33
|
+
...field && { field }
|
|
34
|
+
} : void 0;
|
|
35
|
+
return new HttpProblem(409, "conflict", "Conflict", "Other records still point at this record", blockedBy && { blockedBy });
|
|
36
|
+
};
|
|
37
|
+
//#endregion
|
|
38
|
+
export { blockedDelete, blockingRelation };
|
package/dist/create-admin.js
CHANGED
|
@@ -6,6 +6,7 @@ import { builtInAssistant } from "./assistant/built-in-assistant.js";
|
|
|
6
6
|
import { consoleAuditQueue } from "./audit/console-queue.js";
|
|
7
7
|
import { webauthnPath } from "./better-auth/origins.js";
|
|
8
8
|
import { apiCors, readCorsOrigins } from "./api-cors.js";
|
|
9
|
+
import { readAppName } from "./app-name.js";
|
|
9
10
|
import { authBasePath } from "./better-auth/create-auth.js";
|
|
10
11
|
import { organizationRoutes } from "./better-auth/organizations/admin-routes.js";
|
|
11
12
|
import { statusRoute } from "./better-auth/status-route.js";
|
|
@@ -131,7 +132,7 @@ const createAdmin = ({ resources: appResources, views: appViews = [], pages = []
|
|
|
131
132
|
...auth && { actorNames: betterAuthActorNames(auth) }
|
|
132
133
|
} }
|
|
133
134
|
};
|
|
134
|
-
const meta = createMeta(deps);
|
|
135
|
+
const meta = createMeta(deps, auth?.appName ?? readAppName(globalThis.process?.env ?? {}) ?? "Protobase");
|
|
135
136
|
const onError = createErrorHandler(options.onUnhandledError);
|
|
136
137
|
const guarded = (routes, caller = authenticate) => {
|
|
137
138
|
const app = new Hono();
|
package/dist/error-handler.js
CHANGED
|
@@ -41,4 +41,4 @@ const createErrorHandler = (onUnhandledError) => (error, c) => {
|
|
|
41
41
|
return problemResponse(new HttpProblem(500, "internal-error", titles[500], "The server failed to handle the request"), c.req.path);
|
|
42
42
|
};
|
|
43
43
|
//#endregion
|
|
44
|
-
export { createErrorHandler, problemOf };
|
|
44
|
+
export { createErrorHandler, isDatabaseError, problemOf };
|
package/dist/meta.js
CHANGED
|
@@ -28,7 +28,7 @@ const modelsHash = (deps) => sha256Hex(stableJson(deps.registry.entries.map((ent
|
|
|
28
28
|
* they could use once signed in, which keep their place in the bottom bar and the user menu.
|
|
29
29
|
* The ETag hashes all of it, so it differs per user and per roles.
|
|
30
30
|
*/
|
|
31
|
-
const createMeta = (deps) => {
|
|
31
|
+
const createMeta = (deps, appName) => {
|
|
32
32
|
const base = modelsHash(deps);
|
|
33
33
|
const forCaller = async (session) => {
|
|
34
34
|
const accesses = await Promise.all(deps.registry.entries.map((entry) => requestAccess(deps, entry, session)));
|
|
@@ -55,6 +55,7 @@ const createMeta = (deps) => {
|
|
|
55
55
|
const plugins = pluginsFor(deps.plugins, session.user.roles);
|
|
56
56
|
const sources = sourcesFor(deps.sources, session.user.roles);
|
|
57
57
|
const body = {
|
|
58
|
+
appName,
|
|
58
59
|
resources: visible.map((access) => access.readable),
|
|
59
60
|
views,
|
|
60
61
|
pages,
|
package/dist/write-pipeline.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { HttpProblem, badRequest } from "./problem.js";
|
|
2
|
+
import { isDatabaseError } from "./error-handler.js";
|
|
2
3
|
import { deleteRecord, fetchRecord, insertRecord, restoreRecord } from "./records.js";
|
|
3
4
|
import { tenantScope } from "./tenant.js";
|
|
4
5
|
import { requestAccess, requireOperation, targetFilter, writableFields } from "./request-access.js";
|
|
5
6
|
import { writeTransaction } from "./transactions.js";
|
|
7
|
+
import { blockedDelete } from "./blocked-delete.js";
|
|
6
8
|
import { attachFiles } from "./files/attach.js";
|
|
7
9
|
import { actorSettings, wakeDispatch } from "./changes/actor.js";
|
|
8
10
|
import { validateBody } from "./validate-body.js";
|
|
@@ -71,7 +73,10 @@ const removeOrRestore = async (trx, deps, access, request) => {
|
|
|
71
73
|
await notify(trx, "undelete", current.record, restored.record);
|
|
72
74
|
return shown(access, restored);
|
|
73
75
|
}
|
|
74
|
-
const removed = await deleteRecord(trx, entry, request.key, scope?.tenantValue, shape)
|
|
76
|
+
const removed = await deleteRecord(trx, entry, request.key, scope?.tenantValue, shape).catch(async (error) => {
|
|
77
|
+
if (isDatabaseError(error) && error.code === "23503") throw await blockedDelete(deps, access, error);
|
|
78
|
+
throw error;
|
|
79
|
+
});
|
|
75
80
|
await notify(trx, "delete", current.record, void 0);
|
|
76
81
|
return shown(access, removed);
|
|
77
82
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@protobase/server",
|
|
3
|
-
"version": "0.21.
|
|
3
|
+
"version": "0.21.4",
|
|
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.21.
|
|
34
|
-
"@protobase/query": "0.21.
|
|
35
|
-
"@protobase/schema": "0.21.
|
|
33
|
+
"@protobase/layout": "0.21.4",
|
|
34
|
+
"@protobase/query": "0.21.4",
|
|
35
|
+
"@protobase/schema": "0.21.4",
|
|
36
36
|
"@react-email/render": "^2.1.0",
|
|
37
37
|
"@scalar/hono-api-reference": "^0.12.10",
|
|
38
38
|
"@sentry/core": "^11.7.0",
|