@vsaas/loopback 10.0.0

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.
Files changed (69) hide show
  1. package/LICENSE +25 -0
  2. package/README.md +91 -0
  3. package/common/models/README.md +109 -0
  4. package/common/models/access-token.json +37 -0
  5. package/common/models/acl.json +17 -0
  6. package/common/models/application.json +130 -0
  7. package/common/models/change.json +25 -0
  8. package/common/models/checkpoint.json +14 -0
  9. package/common/models/email.json +11 -0
  10. package/common/models/key-value-model.json +4 -0
  11. package/common/models/role-mapping.json +26 -0
  12. package/common/models/role.json +30 -0
  13. package/common/models/scope.json +14 -0
  14. package/common/models/user.json +118 -0
  15. package/dist/_virtual/_rolldown/runtime.cjs +32 -0
  16. package/dist/common/models/access-token.cjs +144 -0
  17. package/dist/common/models/access-token2.cjs +43 -0
  18. package/dist/common/models/acl.cjs +428 -0
  19. package/dist/common/models/acl2.cjs +27 -0
  20. package/dist/common/models/application.cjs +100 -0
  21. package/dist/common/models/application2.cjs +118 -0
  22. package/dist/common/models/change.cjs +404 -0
  23. package/dist/common/models/change2.cjs +25 -0
  24. package/dist/common/models/checkpoint.cjs +43 -0
  25. package/dist/common/models/checkpoint2.cjs +18 -0
  26. package/dist/common/models/email.cjs +18 -0
  27. package/dist/common/models/email2.cjs +30 -0
  28. package/dist/common/models/key-value-model.cjs +140 -0
  29. package/dist/common/models/key-value-model2.cjs +14 -0
  30. package/dist/common/models/role-mapping.cjs +57 -0
  31. package/dist/common/models/role-mapping2.cjs +34 -0
  32. package/dist/common/models/role.cjs +396 -0
  33. package/dist/common/models/role2.cjs +38 -0
  34. package/dist/common/models/scope.cjs +30 -0
  35. package/dist/common/models/scope2.cjs +21 -0
  36. package/dist/common/models/user.cjs +810 -0
  37. package/dist/common/models/user2.cjs +118 -0
  38. package/dist/index.cjs +16 -0
  39. package/dist/lib/access-context.cjs +228 -0
  40. package/dist/lib/application.cjs +450 -0
  41. package/dist/lib/builtin-models.cjs +60 -0
  42. package/dist/lib/configure-shared-methods.cjs +41 -0
  43. package/dist/lib/connectors/base-connector.cjs +23 -0
  44. package/dist/lib/connectors/mail-direct-transport.cjs +375 -0
  45. package/dist/lib/connectors/mail-stub-transport.cjs +86 -0
  46. package/dist/lib/connectors/mail.cjs +128 -0
  47. package/dist/lib/connectors/memory.cjs +19 -0
  48. package/dist/lib/current-context.cjs +22 -0
  49. package/dist/lib/globalize.cjs +29 -0
  50. package/dist/lib/loopback.cjs +313 -0
  51. package/dist/lib/model.cjs +1009 -0
  52. package/dist/lib/persisted-model.cjs +1835 -0
  53. package/dist/lib/registry.cjs +291 -0
  54. package/dist/lib/runtime.cjs +25 -0
  55. package/dist/lib/server-app.cjs +231 -0
  56. package/dist/lib/utils.cjs +154 -0
  57. package/dist/package.cjs +124 -0
  58. package/dist/server/middleware/context.cjs +7 -0
  59. package/dist/server/middleware/error-handler.cjs +6 -0
  60. package/dist/server/middleware/favicon.cjs +13 -0
  61. package/dist/server/middleware/rest.cjs +44 -0
  62. package/dist/server/middleware/static.cjs +14 -0
  63. package/dist/server/middleware/status.cjs +28 -0
  64. package/dist/server/middleware/token.cjs +66 -0
  65. package/dist/server/middleware/url-not-found.cjs +20 -0
  66. package/favicon.ico +0 -0
  67. package/package.json +121 -0
  68. package/templates/reset-form.ejs +3 -0
  69. package/templates/verify.ejs +9 -0
@@ -0,0 +1,396 @@
1
+ "use strict";
2
+ const require_runtime = require("../../_virtual/_rolldown/runtime.cjs");
3
+ const require_lib_utils = require("../../lib/utils.cjs");
4
+ const require_lib_access_context = require("../../lib/access-context.cjs");
5
+ //#region src/common/models/role.ts
6
+ var require_role = /* @__PURE__ */ require_runtime.__commonJSMin(((exports, module) => {
7
+ const debug = require("debug")("loopback:security:role");
8
+ const assert = require("assert");
9
+ const utils = require_lib_utils;
10
+ const ctx = (require_lib_access_context.init_access_context(), require_runtime.__toCommonJS(require_lib_access_context.access_context_exports));
11
+ const AccessContext = ctx.AccessContext;
12
+ const Principal = ctx.Principal;
13
+ module.exports = function(Role) {
14
+ const RoleMapping = Role.registry.getModelByType("RoleMapping");
15
+ assert(RoleMapping, "RoleMapping model must be defined before Role model");
16
+ Role.resolveRelatedModels = function() {
17
+ if (!this.userModel) {
18
+ const reg = this.registry;
19
+ this.roleMappingModel = reg.getModelByType("RoleMapping");
20
+ this.userModel = reg.getModelByType("User");
21
+ this.applicationModel = reg.getModelByType("Application");
22
+ }
23
+ };
24
+ Role.once("dataSourceAttached", function(roleModel) {
25
+ [
26
+ "users",
27
+ "applications",
28
+ "roles"
29
+ ].forEach(function(rel) {
30
+ Role.prototype[rel] = function(query, callback) {
31
+ if (!callback) if (typeof query === "function") {
32
+ callback = query;
33
+ query = {};
34
+ } else callback = utils.createPromiseCallback();
35
+ query = query || {};
36
+ query.where = query.where || {};
37
+ roleModel.resolveRelatedModels();
38
+ const relsToModels = {
39
+ users: roleModel.userModel,
40
+ applications: roleModel.applicationModel,
41
+ roles: roleModel
42
+ };
43
+ const ACL = roleModel.registry.getModelByType("ACL");
44
+ const relsToTypes = {
45
+ users: ACL.USER,
46
+ applications: ACL.APP,
47
+ roles: ACL.ROLE
48
+ };
49
+ let principalModel = relsToModels[rel];
50
+ let principalType = relsToTypes[rel];
51
+ if (rel === "users" && query.where.principalType && query.where.principalType !== RoleMapping.USER) {
52
+ principalModel = this.constructor.registry.findModel(query.where.principalType);
53
+ principalType = query.where.principalType;
54
+ }
55
+ delete query.where.principalType;
56
+ if (principalModel) listByPrincipalType(this, principalModel, principalType, query, callback);
57
+ else process.nextTick(function() {
58
+ callback(null, []);
59
+ });
60
+ return callback.promise;
61
+ };
62
+ });
63
+ function listByPrincipalType(context, model, principalType, query, callback) {
64
+ if (callback === void 0 && typeof query === "function") {
65
+ callback = query;
66
+ query = {};
67
+ }
68
+ query = query || {};
69
+ roleModel.roleMappingModel.find({ where: {
70
+ roleId: context.id,
71
+ principalType
72
+ } }, function(err, mappings) {
73
+ if (err) return callback(err);
74
+ const ids = mappings.map(function(m) {
75
+ return m.principalId;
76
+ });
77
+ query.where = query.where || {};
78
+ query.where.id = { inq: ids };
79
+ model.find(query, function(error, models) {
80
+ callback(error, models);
81
+ });
82
+ });
83
+ }
84
+ });
85
+ Role.OWNER = "$owner";
86
+ Role.RELATED = "$related";
87
+ Role.AUTHENTICATED = "$authenticated";
88
+ Role.UNAUTHENTICATED = "$unauthenticated";
89
+ Role.EVERYONE = "$everyone";
90
+ Role.registerResolver = function(role, resolver) {
91
+ if (!Role.resolvers) Role.resolvers = {};
92
+ Role.resolvers[role] = resolver;
93
+ };
94
+ Role.registerResolver(Role.OWNER, function(role, context, callback) {
95
+ if (!context || !context.model || !context.modelId) {
96
+ process.nextTick(function() {
97
+ if (callback) callback(null, false);
98
+ });
99
+ return;
100
+ }
101
+ const modelClass = context.model;
102
+ const modelId = context.modelId;
103
+ const user = context.getUser();
104
+ const userId = user && user.id;
105
+ const principalType = user && user.principalType;
106
+ const opts = { accessToken: context.accessToken };
107
+ Role.isOwner(modelClass, modelId, userId, principalType, opts, callback);
108
+ });
109
+ function isUserClass(modelClass) {
110
+ if (!modelClass) return false;
111
+ const User = modelClass.modelBuilder.models.User;
112
+ if (!User) return false;
113
+ return modelClass == User || modelClass.prototype instanceof User;
114
+ }
115
+ function matches(id1, id2) {
116
+ if (id1 === void 0 || id1 === null || id1 === "" || id2 === void 0 || id2 === null || id2 === "") return false;
117
+ return id1 === id2 || id1.toString() === id2.toString();
118
+ }
119
+ Role.isOwner = function isOwner(modelClass, modelId, userId, principalType, options, callback) {
120
+ const registry = this.registry;
121
+ if (!callback && typeof options === "function") {
122
+ callback = options;
123
+ options = {};
124
+ } else if (!callback && typeof principalType === "function") {
125
+ callback = principalType;
126
+ principalType = void 0;
127
+ options = {};
128
+ }
129
+ principalType = principalType || Principal.USER;
130
+ assert(modelClass, "Model class is required");
131
+ if (!callback) callback = utils.createPromiseCallback();
132
+ debug("isOwner(): %s %s userId: %s principalType: %s", modelClass && modelClass.modelName, modelId, userId, principalType);
133
+ if (!userId) {
134
+ debug("isOwner(): no user id was set, returning false");
135
+ process.nextTick(function() {
136
+ callback(null, false);
137
+ });
138
+ return callback.promise;
139
+ }
140
+ const isMultipleUsers = _isMultipleUsers();
141
+ const isPrincipalTypeValid = !isMultipleUsers && principalType === Principal.USER || isMultipleUsers && principalType !== Principal.USER;
142
+ debug("isOwner(): isMultipleUsers?", isMultipleUsers, "isPrincipalTypeValid?", isPrincipalTypeValid);
143
+ if (!isPrincipalTypeValid) {
144
+ process.nextTick(function() {
145
+ callback(null, false);
146
+ });
147
+ return callback.promise;
148
+ }
149
+ if (isUserClass(modelClass)) {
150
+ const userModelName = modelClass.modelName;
151
+ if (principalType === Principal.USER || principalType === userModelName) {
152
+ process.nextTick(function() {
153
+ callback(null, matches(modelId, userId));
154
+ });
155
+ return callback.promise;
156
+ }
157
+ }
158
+ modelClass.findById(modelId, options, function(err, inst) {
159
+ if (err || !inst) {
160
+ debug("Model not found for id %j", modelId);
161
+ return callback(err, false);
162
+ }
163
+ debug("Model found: %j", inst);
164
+ if (!modelClass.settings.ownerRelations) return legacyOwnershipCheck(inst);
165
+ else return checkOwnership(inst);
166
+ });
167
+ return callback.promise;
168
+ function legacyOwnershipCheck(inst) {
169
+ const ownerId = inst.userId || inst.owner;
170
+ if (principalType === Principal.USER && ownerId && "function" !== typeof ownerId) return callback(null, matches(ownerId, userId));
171
+ for (const r in modelClass.relations) {
172
+ const rel = modelClass.relations[r];
173
+ if (!(rel.type === "belongsTo" && isUserClass(rel.modelTo))) continue;
174
+ const relatedUser = rel.modelTo;
175
+ const userModelName = relatedUser.modelName;
176
+ const multipleUsers = _isMultipleUsers(relatedUser);
177
+ if (!multipleUsers && principalType === Principal.USER || multipleUsers && principalType === userModelName) {
178
+ debug("Checking relation %s to %s: %j", r, userModelName, rel);
179
+ inst[r](processRelatedUser);
180
+ return;
181
+ }
182
+ }
183
+ debug("No matching belongsTo relation found for model %j - user %j principalType %j", modelId, userId, principalType);
184
+ callback(null, false);
185
+ function processRelatedUser(err, user) {
186
+ if (err || !user) return callback(err, false);
187
+ debug("User found: %j", user.id);
188
+ callback(null, matches(user.id, userId));
189
+ }
190
+ }
191
+ function checkOwnership(inst) {
192
+ const ownerRelations = inst.constructor.settings.ownerRelations;
193
+ const relWithUsers = [];
194
+ for (const r in modelClass.relations) {
195
+ const rel = modelClass.relations[r];
196
+ if (rel.type !== "belongsTo" || !isUserClass(rel.modelTo)) continue;
197
+ const relatedUser = rel.modelTo;
198
+ const userModelName = relatedUser.modelName;
199
+ const multipleUsers = _isMultipleUsers(relatedUser);
200
+ if (!multipleUsers && principalType === Principal.USER || multipleUsers && principalType === userModelName) {
201
+ debug("Checking relation %s to %s: %j", r, userModelName, rel);
202
+ if (ownerRelations === true) relWithUsers.push(r);
203
+ else if (Array.isArray(ownerRelations) && ownerRelations.indexOf(r) !== -1) relWithUsers.push(r);
204
+ }
205
+ }
206
+ if (relWithUsers.length === 0) {
207
+ debug("No matching belongsTo relation found for model %j and user: %j principalType: %j", modelId, userId, principalType);
208
+ return callback(null, false);
209
+ }
210
+ (async function() {
211
+ for (const r of relWithUsers) {
212
+ const user = await utils.invokeWithCallback(inst[r], inst, []);
213
+ if (!user) continue;
214
+ debug("User found: %j (through %j)", user.id, r);
215
+ if (matches(user.id, userId)) return true;
216
+ }
217
+ return false;
218
+ })().then(function(isOwner) {
219
+ callback(null, isOwner);
220
+ }, function(err) {
221
+ callback(err, false);
222
+ });
223
+ }
224
+ function _isMultipleUsers(userModel) {
225
+ const accessTokensRel = (userModel || registry.getModelByType("User")).relations.accessTokens;
226
+ return !!(accessTokensRel && accessTokensRel.polymorphic);
227
+ }
228
+ };
229
+ Role.registerResolver(Role.AUTHENTICATED, function(role, context, callback) {
230
+ if (!context) {
231
+ process.nextTick(function() {
232
+ if (callback) callback(null, false);
233
+ });
234
+ return;
235
+ }
236
+ Role.isAuthenticated(context, callback);
237
+ });
238
+ Role.isAuthenticated = function isAuthenticated(context, callback) {
239
+ if (!callback) callback = utils.createPromiseCallback();
240
+ process.nextTick(function() {
241
+ if (callback) callback(null, context.isAuthenticated());
242
+ });
243
+ return callback.promise;
244
+ };
245
+ Role.registerResolver(Role.UNAUTHENTICATED, function(role, context, callback) {
246
+ process.nextTick(function() {
247
+ if (callback) callback(null, !context || !context.isAuthenticated());
248
+ });
249
+ });
250
+ Role.registerResolver(Role.EVERYONE, function(role, context, callback) {
251
+ process.nextTick(function() {
252
+ if (callback) callback(null, true);
253
+ });
254
+ });
255
+ Role.isInRole = function(role, context, callback) {
256
+ context.registry = this.registry;
257
+ if (!(context instanceof AccessContext)) context = new AccessContext(context);
258
+ if (!callback) {
259
+ callback = utils.createPromiseCallback();
260
+ callback.promise = callback.promise.then(function(isInRole) {
261
+ return !!isInRole;
262
+ });
263
+ }
264
+ this.resolveRelatedModels();
265
+ debug("isInRole(): %s", role);
266
+ context.debug();
267
+ const resolver = Role.resolvers[role];
268
+ if (resolver) {
269
+ debug("Custom resolver found for role %s", role);
270
+ const promise = resolver(role, context, callback);
271
+ if (promise && typeof promise.then === "function") promise.then(function(result) {
272
+ callback(null, result);
273
+ }, callback);
274
+ return callback.promise;
275
+ }
276
+ if (context.principals.length === 0) {
277
+ debug("isInRole() returns: false");
278
+ process.nextTick(function() {
279
+ if (callback) callback(null, false);
280
+ });
281
+ return callback.promise;
282
+ }
283
+ const inRole = context.principals.some(function(p) {
284
+ const principalType = p.type || void 0;
285
+ const principalId = p.id || void 0;
286
+ return principalType === RoleMapping.ROLE && principalId === role;
287
+ });
288
+ if (inRole) {
289
+ debug("isInRole() returns: %j", inRole);
290
+ process.nextTick(function() {
291
+ if (callback) callback(null, true);
292
+ });
293
+ return callback.promise;
294
+ }
295
+ const roleMappingModel = this.roleMappingModel;
296
+ utils.invokeWithCallback(this.findOne, this, [{ where: { name: role } }]).then(function(result) {
297
+ if (!result) return false;
298
+ debug("Role found: %j", result);
299
+ const roleId = result.id.toString();
300
+ const principalConditions = [];
301
+ for (let i = 0; i < context.principals.length; i++) {
302
+ const principal = context.principals[i];
303
+ const principalType = principal.type || void 0;
304
+ let principalId = principal.id;
305
+ if (principalId !== null && principalId !== void 0 && typeof principalId !== "string") principalId = principalId.toString();
306
+ if (!principalType || principalId === null || principalId === void 0) continue;
307
+ principalConditions.push({
308
+ principalType,
309
+ principalId
310
+ });
311
+ }
312
+ if (principalConditions.length === 0) return false;
313
+ let mappingQuery;
314
+ if (principalConditions.length === 1) mappingQuery = { where: {
315
+ roleId,
316
+ principalType: principalConditions[0].principalType,
317
+ principalId: principalConditions[0].principalId
318
+ } };
319
+ else mappingQuery = { where: { and: [{ roleId }, { or: principalConditions }] } };
320
+ return utils.invokeWithCallback(roleMappingModel.findOne, roleMappingModel, [mappingQuery]).then(function(mapping) {
321
+ debug("Role mapping found: %j", mapping);
322
+ return !!mapping;
323
+ });
324
+ }).then(function(result) {
325
+ debug("isInRole() returns: %j", result);
326
+ if (callback) callback(null, result);
327
+ }, function(err) {
328
+ if (callback) callback(err);
329
+ });
330
+ return callback.promise;
331
+ };
332
+ Role.getRoles = function(context, options, callback) {
333
+ if (!callback) if (typeof options === "function") {
334
+ callback = options;
335
+ options = {};
336
+ } else callback = utils.createPromiseCallback();
337
+ if (!options) options = {};
338
+ context.registry = this.registry;
339
+ if (!(context instanceof AccessContext)) context = new AccessContext(context);
340
+ const roles = [];
341
+ this.resolveRelatedModels();
342
+ const addRole = function(role) {
343
+ if (role && roles.indexOf(role) === -1) roles.push(role);
344
+ };
345
+ const self = this;
346
+ const resolverNames = Object.keys(Role.resolvers);
347
+ const smartRoleChecks = new Array(resolverNames.length);
348
+ for (let i = 0; i < resolverNames.length; i++) {
349
+ const role = resolverNames[i];
350
+ smartRoleChecks[i] = utils.invokeWithCallback(self.isInRole, self, [role, context]).then(function(inRole) {
351
+ if (debug.enabled) debug("In role %j: %j", role, inRole);
352
+ if (inRole) addRole(role);
353
+ });
354
+ }
355
+ const mappingChecks = [];
356
+ const roleMappingModel = this.roleMappingModel;
357
+ for (let i = 0; i < context.principals.length; i++) {
358
+ const p = context.principals[i];
359
+ const principalType = p.type || void 0;
360
+ let principalId = p.id == null ? void 0 : p.id;
361
+ if (typeof principalId !== "string" && principalId != null) principalId = principalId.toString();
362
+ if (principalType === RoleMapping.ROLE && principalId) addRole(principalId);
363
+ if (principalType && principalId) {
364
+ const filter = { where: {
365
+ principalType,
366
+ principalId
367
+ } };
368
+ if (options.returnOnlyRoleNames === true) filter.include = ["role"];
369
+ mappingChecks.push(utils.invokeWithCallback(roleMappingModel.find, roleMappingModel, [filter]).then(function(mappings) {
370
+ debug("Role mappings found: %j", mappings);
371
+ for (let j = 0; j < mappings.length; j++) {
372
+ const m = mappings[j];
373
+ let role;
374
+ if (options.returnOnlyRoleNames === true) role = m.toJSON().role.name;
375
+ else role = m.roleId;
376
+ addRole(role);
377
+ }
378
+ }));
379
+ }
380
+ }
381
+ Promise.all(smartRoleChecks).then(function() {
382
+ return Promise.all(mappingChecks);
383
+ }).then(function() {
384
+ debug("getRoles() returns: %j %j", null, roles);
385
+ if (callback) callback(null, roles);
386
+ }, function(err) {
387
+ debug("getRoles() returns: %j %j", err, roles);
388
+ if (callback) callback(err, roles);
389
+ });
390
+ return callback.promise;
391
+ };
392
+ Role.validatesUniquenessOf("name", { message: "already exists" });
393
+ };
394
+ }));
395
+ //#endregion
396
+ module.exports = require_role();
@@ -0,0 +1,38 @@
1
+ //#region common/models/role.json
2
+ var require_role = /* @__PURE__ */ require("../../_virtual/_rolldown/runtime.cjs").__commonJSMin(((exports, module) => {
3
+ module.exports = {
4
+ "name": "Role",
5
+ "properties": {
6
+ "id": {
7
+ "type": "string",
8
+ "id": true,
9
+ "generated": true
10
+ },
11
+ "name": {
12
+ "type": "string",
13
+ "required": true
14
+ },
15
+ "description": "string",
16
+ "created": {
17
+ "type": "date",
18
+ "defaultFn": "now"
19
+ },
20
+ "modified": {
21
+ "type": "date",
22
+ "defaultFn": "now"
23
+ }
24
+ },
25
+ "relations": { "principals": {
26
+ "type": "hasMany",
27
+ "model": "RoleMapping",
28
+ "foreignKey": "roleId"
29
+ } }
30
+ };
31
+ }));
32
+ //#endregion
33
+ Object.defineProperty(exports, "default", {
34
+ enumerable: true,
35
+ get: function() {
36
+ return require_role();
37
+ }
38
+ });
@@ -0,0 +1,30 @@
1
+ "use strict";
2
+ const require_runtime$1 = require("../../_virtual/_rolldown/runtime.cjs");
3
+ const require_lib_runtime = require("../../lib/runtime.cjs");
4
+ //#region src/common/models/scope.ts
5
+ var require_scope = /* @__PURE__ */ require_runtime$1.__commonJSMin(((exports, module) => {
6
+ const assert = require("assert");
7
+ const runtime = require_lib_runtime;
8
+ function configureScope(Scope) {
9
+ Scope.resolveRelatedModels = function() {
10
+ if (!this.aclModel) {
11
+ const reg = this.registry;
12
+ const loopback = runtime.loopback;
13
+ this.aclModel = reg.getModelByType(loopback.ACL);
14
+ }
15
+ };
16
+ Scope.checkPermission = function(scope, model, property, accessType, callback) {
17
+ this.resolveRelatedModels();
18
+ const aclModel = this.aclModel;
19
+ assert(aclModel, "ACL model must be defined before Scope.checkPermission is called");
20
+ this.findOne({ where: { name: scope } }, function(err, scope) {
21
+ if (err) {
22
+ if (callback) callback(err);
23
+ } else aclModel.checkPermission(aclModel.SCOPE, scope.id, model, property, accessType, callback);
24
+ });
25
+ };
26
+ }
27
+ module.exports = configureScope;
28
+ }));
29
+ //#endregion
30
+ module.exports = require_scope();
@@ -0,0 +1,21 @@
1
+ //#region common/models/scope.json
2
+ var require_scope = /* @__PURE__ */ require("../../_virtual/_rolldown/runtime.cjs").__commonJSMin(((exports, module) => {
3
+ module.exports = {
4
+ "name": "Scope",
5
+ "description": ["Schema for Scope which represents the permissions that are granted", "to client applications by the resource owner"],
6
+ "properties": {
7
+ "name": {
8
+ "type": "string",
9
+ "required": true
10
+ },
11
+ "description": "string"
12
+ }
13
+ };
14
+ }));
15
+ //#endregion
16
+ Object.defineProperty(exports, "default", {
17
+ enumerable: true,
18
+ get: function() {
19
+ return require_scope();
20
+ }
21
+ });