@terreno/api 0.13.0 → 0.13.2
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.js +9 -0
- package/dist/api.test.js +2 -2
- package/dist/consentApp.d.ts +2 -2
- package/dist/consentApp.js +2 -1
- package/dist/githubAuth.test.js +409 -0
- package/dist/models/consentForm.js +8 -9
- package/dist/models/versionConfig.d.ts +1 -1
- package/dist/notifiers/slackNotifier.d.ts +2 -2
- package/dist/notifiers/slackNotifier.js +38 -7
- package/dist/plugins.d.ts +3 -3
- package/dist/plugins.js +8 -4
- package/dist/populate.test.js +5 -1
- package/dist/secretProviders.js +4 -1
- package/dist/tests.js +1 -0
- package/dist/transformers.d.ts +5 -5
- package/dist/transformers.js +38 -37
- package/dist/utils.js +13 -3
- package/package.json +1 -1
- package/src/api.test.ts +2 -2
- package/src/api.ts +9 -0
- package/src/consentApp.ts +3 -3
- package/src/githubAuth.test.ts +327 -0
- package/src/models/consentForm.ts +8 -10
- package/src/models/versionConfig.ts +1 -1
- package/src/notifiers/slackNotifier.ts +7 -6
- package/src/openApiEtag.ts +1 -1
- package/src/plugins.ts +13 -8
- package/src/populate.test.ts +45 -20
- package/src/secretProviders.ts +4 -3
- package/src/tests.ts +18 -14
- package/src/transformers.ts +32 -30
- package/src/utils.ts +13 -3
package/src/secretProviders.ts
CHANGED
|
@@ -86,9 +86,10 @@ export class GcpSecretProvider implements SecretProvider {
|
|
|
86
86
|
const SecretManagerServiceClient =
|
|
87
87
|
mod.SecretManagerServiceClient ?? mod.default?.SecretManagerServiceClient;
|
|
88
88
|
if (!SecretManagerServiceClient) {
|
|
89
|
-
throw new
|
|
90
|
-
|
|
91
|
-
|
|
89
|
+
throw new APIError({
|
|
90
|
+
status: 500,
|
|
91
|
+
title: "SecretManagerServiceClient not found in @google-cloud/secret-manager module",
|
|
92
|
+
});
|
|
92
93
|
}
|
|
93
94
|
this.client = new SecretManagerServiceClient();
|
|
94
95
|
}
|
package/src/tests.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import express, {type Express} from "express";
|
|
2
2
|
import mongoose, {type Model, model, Schema} from "mongoose";
|
|
3
|
-
import passportLocalMongoose from "passport-local-mongoose";
|
|
3
|
+
import passportLocalMongoose, {type PassportLocalMongooseDocument} from "passport-local-mongoose";
|
|
4
4
|
import qs from "qs";
|
|
5
5
|
import supertest from "supertest";
|
|
6
6
|
import type TestAgent from "supertest/lib/agent";
|
|
@@ -62,19 +62,22 @@ const userSchema = new Schema<User>({
|
|
|
62
62
|
username: {description: "The user's username", type: String},
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
userSchema.plugin(
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
65
|
+
userSchema.plugin(
|
|
66
|
+
passportLocalMongoose as unknown as (schema: Schema, options?: Record<string, unknown>) => void,
|
|
67
|
+
{
|
|
68
|
+
attemptsField: "attempts",
|
|
69
|
+
interval: process.env.NODE_ENV === "test" ? 1 : 100,
|
|
70
|
+
limitAttempts: true,
|
|
71
|
+
maxAttempts: 3,
|
|
72
|
+
maxInterval: process.env.NODE_ENV === "test" ? 1 : 300000,
|
|
73
|
+
usernameCaseInsensitive: true,
|
|
74
|
+
usernameField: "email",
|
|
75
|
+
}
|
|
76
|
+
);
|
|
74
77
|
// userSchema.plugin(tokenPlugin);
|
|
75
78
|
userSchema.plugin(createdUpdatedPlugin);
|
|
76
79
|
userSchema.plugin(isDisabledPlugin);
|
|
77
|
-
userSchema.methods.postCreate = async function (body:
|
|
80
|
+
userSchema.methods.postCreate = async function (body: {age?: number}) {
|
|
78
81
|
this.age = body.age;
|
|
79
82
|
return this.save();
|
|
80
83
|
};
|
|
@@ -121,6 +124,7 @@ const foodSchema = new Schema<Food>(
|
|
|
121
124
|
type: Schema.Types.ObjectId,
|
|
122
125
|
},
|
|
123
126
|
],
|
|
127
|
+
// noExplicitAny: DateOnly is a custom SchemaType not recognized by Mongoose's built-in type definitions
|
|
124
128
|
expiration: {description: "Expiration date of the food", type: DateOnly as any},
|
|
125
129
|
hidden: {
|
|
126
130
|
default: false,
|
|
@@ -221,13 +225,13 @@ export const setupDb = async () => {
|
|
|
221
225
|
UserModel.create({admin: true, email: "admin@example.com", name: "Admin"}),
|
|
222
226
|
UserModel.create({admin: true, email: "admin+other@example.com", name: "Admin Other"}),
|
|
223
227
|
]);
|
|
224
|
-
await (notAdmin as
|
|
228
|
+
await (notAdmin as unknown as PassportLocalMongooseDocument).setPassword("password");
|
|
225
229
|
await notAdmin.save();
|
|
226
230
|
|
|
227
|
-
await (admin as
|
|
231
|
+
await (admin as unknown as PassportLocalMongooseDocument).setPassword("securePassword");
|
|
228
232
|
await admin.save();
|
|
229
233
|
|
|
230
|
-
await (adminOther as
|
|
234
|
+
await (adminOther as unknown as PassportLocalMongooseDocument).setPassword("otherPassword");
|
|
231
235
|
|
|
232
236
|
await adminOther.save();
|
|
233
237
|
|
package/src/transformers.ts
CHANGED
|
@@ -15,7 +15,10 @@ export interface TerrenoTransformer<T> {
|
|
|
15
15
|
serialize?: (obj: T, user?: User) => Partial<T> | undefined;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
const getUserType = (
|
|
19
|
+
user?: User,
|
|
20
|
+
obj?: Record<string, unknown>
|
|
21
|
+
): "anon" | "auth" | "owner" | "admin" => {
|
|
19
22
|
if (user?.admin) {
|
|
20
23
|
return "admin";
|
|
21
24
|
}
|
|
@@ -26,10 +29,9 @@ function getUserType(user?: User, obj?: any): "anon" | "auth" | "owner" | "admin
|
|
|
26
29
|
return "auth";
|
|
27
30
|
}
|
|
28
31
|
return "anon";
|
|
29
|
-
}
|
|
32
|
+
};
|
|
30
33
|
|
|
31
|
-
export
|
|
32
|
-
// TODO: do something with KeyOf here.
|
|
34
|
+
export const AdminOwnerTransformer = <T>(options: {
|
|
33
35
|
anonReadFields?: string[];
|
|
34
36
|
authReadFields?: string[];
|
|
35
37
|
ownerReadFields?: string[];
|
|
@@ -38,20 +40,20 @@ export function AdminOwnerTransformer<T>(options: {
|
|
|
38
40
|
authWriteFields?: string[];
|
|
39
41
|
ownerWriteFields?: string[];
|
|
40
42
|
adminWriteFields?: string[];
|
|
41
|
-
}): TerrenoTransformer<T> {
|
|
42
|
-
|
|
43
|
+
}): TerrenoTransformer<T> => {
|
|
44
|
+
const pickFields = (obj: Partial<T>, fields: string[]): Partial<T> => {
|
|
43
45
|
const newData: Partial<T> = {};
|
|
44
46
|
for (const field of fields) {
|
|
45
|
-
if (obj[field] !== undefined) {
|
|
46
|
-
newData[field] = obj[field];
|
|
47
|
+
if ((obj as Record<string, unknown>)[field] !== undefined) {
|
|
48
|
+
(newData as Record<string, unknown>)[field] = (obj as Record<string, unknown>)[field];
|
|
47
49
|
}
|
|
48
50
|
}
|
|
49
51
|
return newData;
|
|
50
|
-
}
|
|
52
|
+
};
|
|
51
53
|
|
|
52
54
|
return {
|
|
53
55
|
serialize: (obj: T, user?: User) => {
|
|
54
|
-
const userType = getUserType(user, obj);
|
|
56
|
+
const userType = getUserType(user, obj as Record<string, unknown>);
|
|
55
57
|
if (userType === "admin") {
|
|
56
58
|
return pickFields(obj, [...(options.adminReadFields ?? []), "id"]);
|
|
57
59
|
}
|
|
@@ -63,10 +65,9 @@ export function AdminOwnerTransformer<T>(options: {
|
|
|
63
65
|
}
|
|
64
66
|
return pickFields(obj, [...(options.anonReadFields ?? []), "id"]);
|
|
65
67
|
},
|
|
66
|
-
// TODO: Migrate AdminOwnerTransform to use pre-hooks.
|
|
67
68
|
transform: (obj: Partial<T>, _method: "create" | "update", user?: User) => {
|
|
68
|
-
const userType = getUserType(user, obj);
|
|
69
|
-
let allowedFields:
|
|
69
|
+
const userType = getUserType(user, obj as Record<string, unknown>);
|
|
70
|
+
let allowedFields: string[];
|
|
70
71
|
if (userType === "admin") {
|
|
71
72
|
allowedFields = options.adminWriteFields ?? [];
|
|
72
73
|
} else if (userType === "owner") {
|
|
@@ -78,21 +79,22 @@ export function AdminOwnerTransformer<T>(options: {
|
|
|
78
79
|
}
|
|
79
80
|
const unallowedFields = Object.keys(obj).filter((k) => !allowedFields.includes(k));
|
|
80
81
|
if (unallowedFields.length) {
|
|
81
|
-
throw new
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
throw new APIError({
|
|
83
|
+
status: 403,
|
|
84
|
+
title: `User of type ${userType} cannot write fields: ${unallowedFields.join(", ")}`,
|
|
85
|
+
});
|
|
84
86
|
}
|
|
85
87
|
return obj;
|
|
86
88
|
},
|
|
87
89
|
};
|
|
88
|
-
}
|
|
90
|
+
};
|
|
89
91
|
|
|
90
|
-
export
|
|
92
|
+
export const transform = <T>(
|
|
91
93
|
options: ModelRouterOptions<T>,
|
|
92
94
|
data: Partial<T> | Partial<T>[],
|
|
93
95
|
method: "create" | "update",
|
|
94
96
|
user?: User
|
|
95
|
-
) {
|
|
97
|
+
) => {
|
|
96
98
|
if (!options.transformer?.transform) {
|
|
97
99
|
return data;
|
|
98
100
|
}
|
|
@@ -108,16 +110,16 @@ export function transform<T>(
|
|
|
108
110
|
return transformFn(data, method, user);
|
|
109
111
|
}
|
|
110
112
|
return data.map((d) => transformFn(d, method, user));
|
|
111
|
-
}
|
|
113
|
+
};
|
|
112
114
|
|
|
113
|
-
export
|
|
115
|
+
export const serialize = <T>(
|
|
114
116
|
req: express.Request,
|
|
115
117
|
options: ModelRouterOptions<T>,
|
|
116
|
-
data: (Document
|
|
117
|
-
) {
|
|
118
|
-
const serializeFn = (serializeData: Document
|
|
118
|
+
data: (Document & T) | (Document & T)[]
|
|
119
|
+
) => {
|
|
120
|
+
const serializeFn = (serializeData: Document & T, serializeUser?: User) => {
|
|
119
121
|
const dataObject = serializeData.toObject() as T;
|
|
120
|
-
(dataObject as
|
|
122
|
+
(dataObject as Record<string, unknown>).id = serializeData._id;
|
|
121
123
|
|
|
122
124
|
// Search for any value that is a Map and transform it to a plain object.
|
|
123
125
|
// Otherwise Express drops the contents.
|
|
@@ -143,18 +145,18 @@ export function serialize<T>(
|
|
|
143
145
|
return serializeFn(data, req.user);
|
|
144
146
|
}
|
|
145
147
|
return data.map((d) => serializeFn(d, req.user));
|
|
146
|
-
}
|
|
148
|
+
};
|
|
147
149
|
|
|
148
150
|
/**
|
|
149
151
|
* Default response handler for modelRouter. Calls toObject on each doc and returns the result,
|
|
150
152
|
* using transformers.serializer if provided.
|
|
151
153
|
*/
|
|
152
|
-
export async
|
|
153
|
-
doc: (Document
|
|
154
|
+
export const defaultResponseHandler = async <T>(
|
|
155
|
+
doc: (Document & T) | (Document & T)[] | null,
|
|
154
156
|
method: "list" | "create" | "read" | "update",
|
|
155
157
|
request: express.Request,
|
|
156
158
|
options: ModelRouterOptions<T>
|
|
157
|
-
) {
|
|
159
|
+
) => {
|
|
158
160
|
if (!doc) {
|
|
159
161
|
return null;
|
|
160
162
|
}
|
|
@@ -168,4 +170,4 @@ export async function defaultResponseHandler<T>(
|
|
|
168
170
|
title: `Error serializing ${method} response: ${errorObj.message}`,
|
|
169
171
|
});
|
|
170
172
|
}
|
|
171
|
-
}
|
|
173
|
+
};
|
package/src/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import mongoose, {Types} from "mongoose";
|
|
2
2
|
|
|
3
|
+
import {APIError} from "./errors";
|
|
3
4
|
import {logger} from "./logger";
|
|
4
5
|
|
|
5
6
|
// A better version of mongoose's ObjectId.isValid,
|
|
@@ -31,17 +32,26 @@ export const checkModelsStrict = (ignoredModels: string[] = []): void => {
|
|
|
31
32
|
const schema = mongoose.model(model).schema;
|
|
32
33
|
|
|
33
34
|
if (schema.get("toObject")?.virtuals !== true) {
|
|
34
|
-
throw new
|
|
35
|
+
throw new APIError({
|
|
36
|
+
status: 500,
|
|
37
|
+
title: `Model ${model} toObject.virtuals not set to true`,
|
|
38
|
+
});
|
|
35
39
|
}
|
|
36
40
|
if (schema.get("toJSON")?.virtuals !== true) {
|
|
37
|
-
throw new
|
|
41
|
+
throw new APIError({
|
|
42
|
+
status: 500,
|
|
43
|
+
title: `Model ${model} toJSON.virtuals not set to true`,
|
|
44
|
+
});
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
if (ignoredModels.includes(model)) {
|
|
41
48
|
continue;
|
|
42
49
|
}
|
|
43
50
|
if (schema.get("strict") !== "throw") {
|
|
44
|
-
throw new
|
|
51
|
+
throw new APIError({
|
|
52
|
+
status: 500,
|
|
53
|
+
title: `Model ${model} is not set to strict mode.`,
|
|
54
|
+
});
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
};
|