@rebasepro/server 0.9.1-canary.26fe4b2 → 0.9.1-canary.29ed165
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/api/errors.d.ts +16 -1
- package/dist/api/rest/write-validation.d.ts +3 -0
- package/dist/index.es.js +43 -5
- package/dist/index.es.js.map +1 -1
- package/package.json +5 -5
package/dist/api/errors.d.ts
CHANGED
|
@@ -9,9 +9,24 @@ export declare class ApiError extends Error {
|
|
|
9
9
|
readonly statusCode: number;
|
|
10
10
|
readonly code: string;
|
|
11
11
|
readonly details?: unknown;
|
|
12
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Whether this outcome is a routine part of normal operation rather than
|
|
14
|
+
* something an operator should look at. Expected errors log at debug; every
|
|
15
|
+
* other operational error logs at warn.
|
|
16
|
+
*
|
|
17
|
+
* The motivating case is `POST /auth/refresh` with no session: clients
|
|
18
|
+
* refresh on page load before they know whether one exists, so every
|
|
19
|
+
* anonymous page view is a 401 — correct, and not worth a warning line.
|
|
20
|
+
*/
|
|
21
|
+
readonly expected: boolean;
|
|
22
|
+
constructor(statusCode: number, code: string, message: string, details?: unknown, expected?: boolean);
|
|
13
23
|
static badRequest(message: string, code?: string, details?: unknown): ApiError;
|
|
14
24
|
static unauthorized(message: string, code?: string): ApiError;
|
|
25
|
+
/**
|
|
26
|
+
* A 401 that is a normal outcome, not an incident — logged at debug.
|
|
27
|
+
* See {@link ApiError.expected}.
|
|
28
|
+
*/
|
|
29
|
+
static unauthenticated(message: string, code?: string): ApiError;
|
|
15
30
|
static forbidden(message: string, code?: string): ApiError;
|
|
16
31
|
static notFound(message: string, code?: string): ApiError;
|
|
17
32
|
static conflict(message: string, code?: string): ApiError;
|
|
@@ -12,8 +12,11 @@ import { CollectionConfig } from "@rebasepro/types";
|
|
|
12
12
|
* columns, so the set is exact);
|
|
13
13
|
* - the foreign-key column behind an owning relation, which callers may write
|
|
14
14
|
* directly instead of through the relation property;
|
|
15
|
+
* - anything named in `options.extraKnownFields` — for an auth collection the
|
|
16
|
+
* credential keys the auth adapter consumes before a row is ever built;
|
|
15
17
|
* - nothing else. `id` in particular is not automatically known — see below.
|
|
16
18
|
*/
|
|
17
19
|
export declare function assertKnownWriteFields(values: Record<string, unknown>, collection: CollectionConfig, options?: {
|
|
18
20
|
rowIndex?: number;
|
|
21
|
+
extraKnownFields?: readonly string[];
|
|
19
22
|
}): void;
|
package/dist/index.es.js
CHANGED
|
@@ -3539,12 +3539,23 @@ var ApiError = class ApiError extends Error {
|
|
|
3539
3539
|
statusCode;
|
|
3540
3540
|
code;
|
|
3541
3541
|
details;
|
|
3542
|
-
|
|
3542
|
+
/**
|
|
3543
|
+
* Whether this outcome is a routine part of normal operation rather than
|
|
3544
|
+
* something an operator should look at. Expected errors log at debug; every
|
|
3545
|
+
* other operational error logs at warn.
|
|
3546
|
+
*
|
|
3547
|
+
* The motivating case is `POST /auth/refresh` with no session: clients
|
|
3548
|
+
* refresh on page load before they know whether one exists, so every
|
|
3549
|
+
* anonymous page view is a 401 — correct, and not worth a warning line.
|
|
3550
|
+
*/
|
|
3551
|
+
expected;
|
|
3552
|
+
constructor(statusCode, code, message, details, expected = false) {
|
|
3543
3553
|
super(message);
|
|
3544
3554
|
this.name = "ApiError";
|
|
3545
3555
|
this.statusCode = statusCode;
|
|
3546
3556
|
this.code = code;
|
|
3547
3557
|
this.details = details;
|
|
3558
|
+
this.expected = expected;
|
|
3548
3559
|
}
|
|
3549
3560
|
static badRequest(message, code = "BAD_REQUEST", details) {
|
|
3550
3561
|
return new ApiError(400, code, message, details);
|
|
@@ -3552,6 +3563,13 @@ var ApiError = class ApiError extends Error {
|
|
|
3552
3563
|
static unauthorized(message, code = "UNAUTHORIZED") {
|
|
3553
3564
|
return new ApiError(401, code, message);
|
|
3554
3565
|
}
|
|
3566
|
+
/**
|
|
3567
|
+
* A 401 that is a normal outcome, not an incident — logged at debug.
|
|
3568
|
+
* See {@link ApiError.expected}.
|
|
3569
|
+
*/
|
|
3570
|
+
static unauthenticated(message, code = "UNAUTHORIZED") {
|
|
3571
|
+
return new ApiError(401, code, message, void 0, true);
|
|
3572
|
+
}
|
|
3555
3573
|
static forbidden(message, code = "FORBIDDEN") {
|
|
3556
3574
|
return new ApiError(403, code, message);
|
|
3557
3575
|
}
|
|
@@ -3584,7 +3602,10 @@ var errorHandler = (err, c) => {
|
|
|
3584
3602
|
const error = err;
|
|
3585
3603
|
const reqId = typeof c.get === "function" ? c.get("requestId") : void 0;
|
|
3586
3604
|
if (error instanceof ApiError || error.name === "ApiError") {
|
|
3587
|
-
|
|
3605
|
+
const expected = error instanceof ApiError && error.expected;
|
|
3606
|
+
const line = `[API] ${c.req.method} ${c.req.path} → ${error.statusCode} ${error.code}: ${error.message}` + (reqId ? ` [${reqId}]` : "");
|
|
3607
|
+
if (expected) logger.debug(line);
|
|
3608
|
+
else logger.warn(`⚠️ ${line}`);
|
|
3588
3609
|
return c.json({ error: {
|
|
3589
3610
|
message: error.message,
|
|
3590
3611
|
code: error.code || "INTERNAL_ERROR",
|
|
@@ -3826,6 +3847,8 @@ function parseQueryOptions(query) {
|
|
|
3826
3847
|
* columns, so the set is exact);
|
|
3827
3848
|
* - the foreign-key column behind an owning relation, which callers may write
|
|
3828
3849
|
* directly instead of through the relation property;
|
|
3850
|
+
* - anything named in `options.extraKnownFields` — for an auth collection the
|
|
3851
|
+
* credential keys the auth adapter consumes before a row is ever built;
|
|
3829
3852
|
* - nothing else. `id` in particular is not automatically known — see below.
|
|
3830
3853
|
*/
|
|
3831
3854
|
function assertKnownWriteFields(values, collection, options) {
|
|
@@ -3833,6 +3856,7 @@ function assertKnownWriteFields(values, collection, options) {
|
|
|
3833
3856
|
if (!collection.properties || Object.keys(collection.properties).length === 0) return;
|
|
3834
3857
|
const known = new Set(Object.keys(collection.properties));
|
|
3835
3858
|
for (const relation of Object.values(resolveCollectionRelations(collection))) if (relation.localKey) known.add(relation.localKey);
|
|
3859
|
+
for (const field of options?.extraKnownFields ?? []) known.add(field);
|
|
3836
3860
|
const unknown = Object.keys(values).filter((key) => !known.has(key));
|
|
3837
3861
|
if (unknown.length === 0) return;
|
|
3838
3862
|
const where = options?.rowIndex !== void 0 ? `Row ${options.rowIndex}: ` : "";
|
|
@@ -4081,9 +4105,13 @@ var RestApiGenerator = class {
|
|
|
4081
4105
|
const body = await parseJsonBody(c);
|
|
4082
4106
|
const isAuth = collection.auth;
|
|
4083
4107
|
const isAuthCollection = isAuth === true || isAuth && typeof isAuth === "object" && isAuth.enabled === true;
|
|
4108
|
+
const collectionAuthConfig = typeof isAuth === "object" ? isAuth : void 0;
|
|
4084
4109
|
if (!isAuthCollection) assertKnownWriteFields(body, resolvedCollection);
|
|
4110
|
+
else {
|
|
4111
|
+
const contract = this.authAdapter?.describeUserCreationContract?.(collectionAuthConfig);
|
|
4112
|
+
if (contract?.validate) assertKnownWriteFields(body, resolvedCollection, { extraKnownFields: contract.extraFields });
|
|
4113
|
+
}
|
|
4085
4114
|
if (isAuthCollection && this.authAdapter?.prepareUserCreation) {
|
|
4086
|
-
const collectionAuthConfig = typeof isAuth === "object" ? isAuth : void 0;
|
|
4087
4115
|
const prepared = await this.authAdapter.prepareUserCreation(body, collectionAuthConfig);
|
|
4088
4116
|
const entity = await driver.save({
|
|
4089
4117
|
path,
|
|
@@ -10347,7 +10375,7 @@ function createAuthRoutes(config) {
|
|
|
10347
10375
|
oldPassword: string().min(1, "Old password is required").max(128),
|
|
10348
10376
|
newPassword: string().min(1, "New password is required").max(128)
|
|
10349
10377
|
});
|
|
10350
|
-
const refreshSchema = object({ refreshToken:
|
|
10378
|
+
const refreshSchema = object({ refreshToken: string().min(1).optional() });
|
|
10351
10379
|
object({ refreshToken: string().optional() });
|
|
10352
10380
|
object({
|
|
10353
10381
|
displayName: string().max(255).optional(),
|
|
@@ -10731,7 +10759,7 @@ function createAuthRoutes(config) {
|
|
|
10731
10759
|
*/
|
|
10732
10760
|
router.post("/refresh", async (c) => {
|
|
10733
10761
|
const refreshToken = readRefreshToken(c, parseBody(refreshSchema, await c.req.json().catch(() => ({}))), config.cookieAuth);
|
|
10734
|
-
if (!refreshToken) throw ApiError.
|
|
10762
|
+
if (!refreshToken) throw ApiError.unauthenticated("No refresh token presented", "NO_SESSION");
|
|
10735
10763
|
const tokenHash = hashRefreshToken(refreshToken);
|
|
10736
10764
|
const storedToken = await authRepo.findRefreshTokenByHash(tokenHash);
|
|
10737
10765
|
if (!storedToken) {
|
|
@@ -11253,6 +11281,16 @@ function createBuiltinAuthAdapter(config) {
|
|
|
11253
11281
|
collectionAuthConfig: collectionAuth ?? collectionAuthConfig
|
|
11254
11282
|
});
|
|
11255
11283
|
},
|
|
11284
|
+
describeUserCreationContract(collectionAuth) {
|
|
11285
|
+
if ((collectionAuth ?? collectionAuthConfig)?.onCreateUser || resolvedOps.onAdminCreateUser) return {
|
|
11286
|
+
validate: false,
|
|
11287
|
+
extraFields: []
|
|
11288
|
+
};
|
|
11289
|
+
return {
|
|
11290
|
+
validate: true,
|
|
11291
|
+
extraFields: ["password"]
|
|
11292
|
+
};
|
|
11293
|
+
},
|
|
11256
11294
|
async finalizeUserCreation(entity, clearPassword) {
|
|
11257
11295
|
return finalizeAdminUserCreation(entity, clearPassword, {
|
|
11258
11296
|
authRepo: authRepository,
|