@tsed/formio 8.0.1 → 8.0.3

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 (65) hide show
  1. package/package.json +11 -10
  2. package/src/FormioModule.spec.ts +113 -0
  3. package/src/FormioModule.ts +130 -0
  4. package/src/builder/FormioMapper.spec.ts +93 -0
  5. package/src/builder/FormioMapper.ts +71 -0
  6. package/src/components/AlterActions.spec.ts +376 -0
  7. package/src/components/AlterActions.ts +137 -0
  8. package/src/components/AlterAudit.spec.ts +19 -0
  9. package/src/components/AlterAudit.ts +12 -0
  10. package/src/components/AlterHost.spec.ts +20 -0
  11. package/src/components/AlterHost.ts +11 -0
  12. package/src/components/AlterLog.spec.ts +19 -0
  13. package/src/components/AlterLog.ts +12 -0
  14. package/src/components/AlterSkip.spec.ts +44 -0
  15. package/src/components/AlterSkip.ts +28 -0
  16. package/src/components/AlterTemplateExportSteps.spec.ts +99 -0
  17. package/src/components/AlterTemplateExportSteps.ts +58 -0
  18. package/src/components/AlterTemplateImportSteps.spec.ts +70 -0
  19. package/src/components/AlterTemplateImportSteps.ts +50 -0
  20. package/src/decorators/action.ts +20 -0
  21. package/src/decorators/actionCtx.spec.ts +25 -0
  22. package/src/decorators/actionCtx.ts +29 -0
  23. package/src/decorators/alter.spec.ts +16 -0
  24. package/src/decorators/alter.ts +19 -0
  25. package/src/decorators/on.spec.ts +16 -0
  26. package/src/decorators/on.ts +19 -0
  27. package/src/decorators/useFormioAuth.spec.ts +15 -0
  28. package/src/decorators/useFormioAuth.ts +12 -0
  29. package/src/domain/AlterHook.ts +3 -0
  30. package/src/domain/Formio.ts +122 -0
  31. package/src/domain/FormioAction.ts +30 -0
  32. package/src/domain/FormioActionsIndex.ts +19 -0
  33. package/src/domain/FormioAuth.ts +83 -0
  34. package/src/domain/FormioBaseModel.ts +14 -0
  35. package/src/domain/FormioConfig.ts +63 -0
  36. package/src/domain/FormioCtxMapper.ts +8 -0
  37. package/src/domain/FormioDecodedToken.ts +13 -0
  38. package/src/domain/FormioErrors.ts +53 -0
  39. package/src/domain/FormioHooks.ts +207 -0
  40. package/src/domain/FormioJs.ts +18 -0
  41. package/src/domain/FormioModels.ts +48 -0
  42. package/src/domain/FormioRouter.ts +10 -0
  43. package/src/domain/FormioSettings.ts +61 -0
  44. package/src/domain/FormioTemplate.ts +9 -0
  45. package/src/domain/FormioTemplateUtil.ts +15 -0
  46. package/src/domain/FormioUpdate.ts +23 -0
  47. package/src/domain/FormioUtils.ts +331 -0
  48. package/src/domain/OnHook.ts +3 -0
  49. package/src/domain/Resource.ts +21 -0
  50. package/src/index.ts +46 -0
  51. package/src/middlewares/FormioAuthMiddleware.spec.ts +61 -0
  52. package/src/middlewares/FormioAuthMiddleware.ts +33 -0
  53. package/src/services/FormioAuthService.spec.ts +396 -0
  54. package/src/services/FormioAuthService.ts +227 -0
  55. package/src/services/FormioDatabase.spec.ts +326 -0
  56. package/src/services/FormioDatabase.ts +165 -0
  57. package/src/services/FormioHooksService.spec.ts +156 -0
  58. package/src/services/FormioHooksService.ts +91 -0
  59. package/src/services/FormioInstaller.spec.ts +146 -0
  60. package/src/services/FormioInstaller.ts +46 -0
  61. package/src/services/FormioRepository.spec.ts +114 -0
  62. package/src/services/FormioRepository.ts +49 -0
  63. package/src/services/FormioService.spec.ts +368 -0
  64. package/src/services/FormioService.ts +133 -0
  65. package/src/utils/isMongoId.ts +3 -0
@@ -0,0 +1,396 @@
1
+ import {catchAsyncError} from "@tsed/core";
2
+ import {BadRequest} from "@tsed/exceptions";
3
+ import {PlatformTest} from "@tsed/platform-http/testing";
4
+
5
+ import {FormioAuthService} from "./FormioAuthService.js";
6
+ import {FormioHooksService} from "./FormioHooksService.js";
7
+ import {FormioService} from "./FormioService.js";
8
+
9
+ function createSubmissionModelFixture(): any {
10
+ return class {
11
+ static findOne = vi.fn().mockReturnThis();
12
+ static updateOne = vi.fn().mockResolvedValue({});
13
+ static lean = vi.fn().mockReturnThis();
14
+ static exec = vi.fn().mockReturnThis();
15
+ save = vi.fn().mockReturnThis();
16
+
17
+ constructor(public ctrOptions: any) {}
18
+
19
+ get form(): string {
20
+ return this.ctrOptions.form;
21
+ }
22
+
23
+ set form(form: string) {
24
+ this.ctrOptions.form = form;
25
+ }
26
+
27
+ toObject() {
28
+ return this.ctrOptions;
29
+ }
30
+ };
31
+ }
32
+
33
+ function createFormModelFixture(): any {
34
+ return class {
35
+ static findOne = vi.fn().mockReturnThis();
36
+ static lean = vi.fn().mockReturnThis();
37
+ static exec = vi.fn();
38
+ };
39
+ }
40
+
41
+ function createRoleModelFixture(): any {
42
+ return class {
43
+ static find = vi.fn().mockReturnThis();
44
+ static sort = vi.fn().mockReturnThis();
45
+ static lean = vi.fn().mockReturnThis();
46
+ static exec = vi.fn().mockResolvedValue({});
47
+ };
48
+ }
49
+
50
+ async function createServiceFixture() {
51
+ const formioService = {
52
+ audit: vi.fn(),
53
+ auth: {
54
+ tempToken: vi.fn(),
55
+ logout: vi.fn(),
56
+ currentUser: vi.fn().mockImplementation((req, res, next) => next()),
57
+ getToken: vi.fn().mockReturnValue("auth_token")
58
+ },
59
+ mongoose: {
60
+ models: {
61
+ submission: createSubmissionModelFixture(),
62
+ form: createFormModelFixture(),
63
+ role: createRoleModelFixture()
64
+ }
65
+ },
66
+ util: {
67
+ idToBson: vi.fn().mockImplementation((f) => f),
68
+ errorCodes: {
69
+ role: {EROLESLOAD: "EROLESLOAD"}
70
+ }
71
+ }
72
+ };
73
+
74
+ const formioHooksService = {
75
+ alter: vi.fn().mockImplementation((event: string, value: any) => value),
76
+ alterAsync: vi.fn().mockImplementation((event: string, value: any) => Promise.resolve(value))
77
+ };
78
+
79
+ const service = await PlatformTest.invoke<FormioAuthService>(FormioAuthService, [
80
+ {
81
+ token: FormioService,
82
+ use: formioService
83
+ },
84
+ {
85
+ token: FormioHooksService,
86
+ use: formioHooksService
87
+ }
88
+ ]);
89
+
90
+ return {service, formioService, formioHooksService};
91
+ }
92
+
93
+ describe("FormioAuthService", () => {
94
+ beforeEach(() => PlatformTest.create());
95
+ afterEach(PlatformTest.reset);
96
+
97
+ describe("createUser()", () => {
98
+ it("should create a user submission", async () => {
99
+ const {service} = await createServiceFixture();
100
+
101
+ const submission = await service.createUser({
102
+ form: "formId",
103
+ data: {
104
+ fullname: "fullname"
105
+ }
106
+ });
107
+
108
+ expect(submission.form).toEqual("formId");
109
+ expect(submission.data).toEqual({
110
+ fullname: "fullname"
111
+ });
112
+ });
113
+ });
114
+ describe("updateUser()", () => {
115
+ it("should update a user submission", async () => {
116
+ const {service, formioService} = await createServiceFixture();
117
+
118
+ const result = await service.updateUser({
119
+ _id: "id",
120
+ form: "formId",
121
+ data: {
122
+ fullname: "fullname"
123
+ }
124
+ });
125
+
126
+ expect(result).toEqual({
127
+ _id: "id",
128
+ form: "formId",
129
+ data: {
130
+ fullname: "fullname"
131
+ }
132
+ });
133
+ expect(formioService.mongoose.models.submission.updateOne).toHaveBeenCalledWith(
134
+ {_id: "id"},
135
+ {$set: {_id: "id", data: {fullname: "fullname"}, form: "formId"}}
136
+ );
137
+ });
138
+ });
139
+ describe("getRoles()", () => {
140
+ it("should return all formio roles", async () => {
141
+ const {service, formioService} = await createServiceFixture();
142
+
143
+ formioService.mongoose.models.role.exec.mockResolvedValue([{name: "administrator"}]);
144
+
145
+ const roles = await service.getRoles({} as any);
146
+
147
+ expect(roles).toEqual([{name: "administrator"}]);
148
+ expect(formioService.mongoose.models.role.find).toHaveBeenCalledWith({deleted: {$eq: null}});
149
+ expect(formioService.mongoose.models.role.sort).toHaveBeenCalledWith({title: 1});
150
+ expect(formioService.mongoose.models.role.lean).toHaveBeenCalledWith();
151
+ });
152
+
153
+ it("should throw an error", async () => {
154
+ const {service, formioService} = await createServiceFixture();
155
+
156
+ vi.mocked(formioService.mongoose.models.role.exec).mockRejectedValue(new Error("test"));
157
+
158
+ const error = await catchAsyncError(() => service.getRoles({} as any));
159
+ expect(error).toBeInstanceOf(BadRequest);
160
+ expect(error?.message).toEqual("EROLESLOAD");
161
+ });
162
+ });
163
+ describe("updateUserRole()", () => {
164
+ it("should update the role associated to the submission", async () => {
165
+ const {service, formioService, formioHooksService} = await createServiceFixture();
166
+ const submission = {
167
+ _id: "submissionId",
168
+ save: vi.fn()
169
+ };
170
+
171
+ formioService.mongoose.models.submission.exec = vi.fn().mockResolvedValue(submission);
172
+
173
+ const user = await service.updateUserRole("submissionId", "roleId", {} as any);
174
+
175
+ expect(formioHooksService.alter).toHaveBeenCalledWith("submissionQuery", {_id: "submissionId", deleted: {$eq: null}}, {});
176
+ expect(formioService.mongoose.models.submission.findOne).toHaveBeenCalledWith({_id: "submissionId", deleted: {$eq: null}});
177
+ expect(user.save).toHaveBeenCalledWith();
178
+ expect(user.roles).toEqual(["roleId"]);
179
+ });
180
+
181
+ it("should update the role associated to the submission without save", async () => {
182
+ const {service, formioService, formioHooksService} = await createServiceFixture();
183
+ const submission = {
184
+ _id: "submissionId"
185
+ };
186
+
187
+ formioService.mongoose.models.submission.exec = vi.fn().mockResolvedValue(submission);
188
+
189
+ const user = await service.updateUserRole("submissionId", "roleId", {} as any);
190
+
191
+ expect(formioHooksService.alter).toHaveBeenCalledWith("submissionQuery", {_id: "submissionId", deleted: {$eq: null}}, {});
192
+ expect(formioService.mongoose.models.submission.findOne).toHaveBeenCalledWith({_id: "submissionId", deleted: {$eq: null}});
193
+ expect(user.roles).toEqual(["roleId"]);
194
+ });
195
+ it("should throw an error when submission doesn't exists", async () => {
196
+ const {service, formioService} = await createServiceFixture();
197
+
198
+ formioService.mongoose.models.submission.exec = vi.fn().mockResolvedValue(null);
199
+
200
+ const error = await catchAsyncError(() => service.updateUserRole("submissionId", "roleId", {} as any));
201
+
202
+ expect(error).toBeInstanceOf(BadRequest);
203
+ });
204
+ });
205
+ describe("setCurrentUser()", () => {
206
+ it("should set current user", async () => {
207
+ const {service} = await createServiceFixture();
208
+ const ctx = PlatformTest.createRequestContext();
209
+ ctx.getRequest().submission = {};
210
+ const user = {
211
+ _id: "id",
212
+ data: {}
213
+ };
214
+
215
+ const token = {
216
+ decoded: {
217
+ form: {
218
+ _id: "id"
219
+ },
220
+ user: {
221
+ _id: "id"
222
+ }
223
+ },
224
+ token: "token"
225
+ };
226
+
227
+ service.setCurrentUser(user as any, token, ctx);
228
+
229
+ expect(ctx.getRequest().submission).toEqual({data: {}});
230
+ expect(ctx.getRequest().user).toEqual({_id: "id", data: {}});
231
+ expect(ctx.getRequest().token).toEqual({
232
+ form: {
233
+ _id: "id"
234
+ },
235
+ user: {
236
+ _id: "id"
237
+ }
238
+ });
239
+ expect(ctx.getResponse().token).toEqual("token");
240
+ expect(ctx.getRequest()["x-jwt-token"]).toEqual("token");
241
+ });
242
+ });
243
+ describe("generatePayloadToken()", () => {
244
+ it("should return the payload token", async () => {
245
+ const {service, formioService, formioHooksService} = await createServiceFixture();
246
+ const ctx = PlatformTest.createRequestContext();
247
+ const user = {
248
+ _id: "id",
249
+ form: "605f0d40fe971372e448bcad",
250
+ data: {}
251
+ };
252
+ const form = {
253
+ _id: "605f0d40fe971372e448bcad"
254
+ };
255
+
256
+ const payload = {
257
+ user: {
258
+ _id: user._id
259
+ },
260
+ form: {
261
+ _id: form._id
262
+ }
263
+ };
264
+
265
+ formioService.mongoose.models.form.exec.mockResolvedValue(form);
266
+
267
+ const result = await service.generatePayloadToken(user as any, ctx);
268
+
269
+ expect(result).toEqual({
270
+ token: {
271
+ decoded: payload,
272
+ token: "auth_token"
273
+ },
274
+ user: {
275
+ _id: "id",
276
+ data: {},
277
+ form: "605f0d40fe971372e448bcad"
278
+ }
279
+ });
280
+ expect(formioHooksService.alter).toHaveBeenCalledWith("token", payload, form, ctx.getRequest());
281
+ expect(formioHooksService.alter).toHaveBeenCalledWith("tokenDecode", payload, ctx.getRequest());
282
+ expect(formioHooksService.alterAsync).toHaveBeenCalledWith("user", user);
283
+ expect(formioHooksService.alterAsync).toHaveBeenCalledWith("login", user, ctx.getRequest());
284
+ });
285
+ it("should throw error when the getForm throw error", async () => {
286
+ const {service, formioService} = await createServiceFixture();
287
+ const ctx = PlatformTest.createRequestContext();
288
+ const user = {
289
+ _id: "id",
290
+ form: "605f0d40fe971372e448bcad",
291
+ data: {}
292
+ };
293
+
294
+ formioService.mongoose.models.form.exec.mockRejectedValue(new Error("message"));
295
+
296
+ const error = await catchAsyncError(() => service.generatePayloadToken(user as any, ctx));
297
+
298
+ expect(error?.name).toEqual("Error");
299
+ expect(formioService.audit).toHaveBeenCalledWith(
300
+ "EAUTH_USERFORM",
301
+ {...ctx.request.raw, userId: user._id},
302
+ "605f0d40fe971372e448bcad",
303
+ error
304
+ );
305
+ });
306
+ it("should throw error when the form isn't found", async () => {
307
+ const {service, formioService} = await createServiceFixture();
308
+ const ctx = PlatformTest.createRequestContext();
309
+ const user = {
310
+ _id: "id",
311
+ form: "605f0d40fe971372e448bcad",
312
+ data: {}
313
+ };
314
+
315
+ formioService.mongoose.models.form.exec.mockResolvedValue(null);
316
+
317
+ const error = await catchAsyncError(() => service.generatePayloadToken(user as any, ctx));
318
+
319
+ expect(error?.name).toEqual("NOT_FOUND");
320
+ expect(formioService.audit).toHaveBeenCalledWith(
321
+ "EAUTH_USERFORM",
322
+ {...ctx.request.raw, userId: user._id},
323
+ "605f0d40fe971372e448bcad",
324
+ {
325
+ message: "User form not found"
326
+ }
327
+ );
328
+ });
329
+ });
330
+ describe("generateSession()", () => {
331
+ it("should generate session", async () => {
332
+ const {service, formioService} = await createServiceFixture();
333
+ const ctx = PlatformTest.createRequestContext();
334
+ const user = {
335
+ _id: "id",
336
+ form: "605f0d40fe971372e448bcad",
337
+ data: {}
338
+ };
339
+
340
+ vi.spyOn(service, "setCurrentUser").mockReturnValue(undefined as any);
341
+ vi.spyOn(service, "generatePayloadToken").mockResolvedValue({
342
+ user,
343
+ token: {
344
+ token: "token"
345
+ }
346
+ } as any);
347
+
348
+ await service.generateSession(user as any, ctx);
349
+
350
+ expect(service.setCurrentUser).toHaveBeenCalledWith(
351
+ user,
352
+ {
353
+ token: "token"
354
+ },
355
+ ctx
356
+ );
357
+ expect(service.setCurrentUser).toHaveBeenCalledWith(
358
+ user,
359
+ {
360
+ token: "token"
361
+ },
362
+ ctx
363
+ );
364
+ expect(formioService.auth.currentUser).toHaveBeenCalledWith(ctx.getRequest(), ctx.getResponse(), expect.any(Function));
365
+ });
366
+ it("should throw an error when an action isn't permitted", async () => {
367
+ const {service} = await createServiceFixture();
368
+ const ctx = PlatformTest.createRequestContext();
369
+ const user = {
370
+ _id: "id",
371
+ form: "605f0d40fe971372e448bcad",
372
+ data: {}
373
+ };
374
+
375
+ vi.spyOn(service, "setCurrentUser").mockReturnValue(undefined as any);
376
+ vi.spyOn(service, "generatePayloadToken").mockRejectedValue(new Error("Not found"));
377
+
378
+ const error = await catchAsyncError(() => service.generateSession(user as any, ctx));
379
+ expect(error?.message).toEqual("Not found");
380
+ });
381
+ });
382
+ describe("tempToken()", () => {
383
+ it("should return tempToken", async () => {
384
+ const {service, formioService} = await createServiceFixture();
385
+
386
+ expect(service.tempToken).toEqual(formioService.auth.tempToken);
387
+ });
388
+ });
389
+ describe("logout()", () => {
390
+ it("should return logout", async () => {
391
+ const {service, formioService} = await createServiceFixture();
392
+
393
+ expect(service.logout).toEqual(formioService.auth.logout);
394
+ });
395
+ });
396
+ });
@@ -0,0 +1,227 @@
1
+ import {isFunction} from "@tsed/core";
2
+ import {Inject, Injectable} from "@tsed/di";
3
+ import {BadRequest, NotFound, Unauthorized} from "@tsed/exceptions";
4
+ import {PlatformContext, Req} from "@tsed/platform-http";
5
+ import {promisify} from "util";
6
+
7
+ import {FormioPayloadToken} from "../domain/FormioDecodedToken.js";
8
+ import {FormioForm, FormioSubmission, WithID} from "../domain/FormioModels.js";
9
+ import {FormioDatabase} from "./FormioDatabase.js";
10
+ import {FormioHooksService} from "./FormioHooksService.js";
11
+ import {FormioService} from "./FormioService.js";
12
+
13
+ @Injectable()
14
+ export class FormioAuthService {
15
+ @Inject()
16
+ formio: FormioService;
17
+
18
+ @Inject()
19
+ hooks: FormioHooksService;
20
+
21
+ @Inject()
22
+ db: FormioDatabase;
23
+
24
+ get currentUser() {
25
+ return promisify(this.formio.auth.currentUser);
26
+ }
27
+
28
+ get getToken() {
29
+ return this.formio.auth.getToken;
30
+ }
31
+
32
+ get tempToken() {
33
+ return this.formio.auth.tempToken;
34
+ }
35
+
36
+ get logout() {
37
+ return this.formio.auth.logout;
38
+ }
39
+
40
+ setCurrentUser<User = any>(user: WithID<FormioSubmission<User>>, token: FormioPayloadToken, ctx: PlatformContext) {
41
+ const request = ctx.getRequest();
42
+ const response = ctx.getResponse();
43
+
44
+ request.submission.data = user.data;
45
+ request.user = user;
46
+ request.token = token.decoded;
47
+ response.token = token.token;
48
+ request["x-jwt-token"] = token.token;
49
+
50
+ return this;
51
+ }
52
+
53
+ /**
54
+ * Generate the payload token for the session
55
+ * @param user
56
+ * @param ctx
57
+ */
58
+ async generatePayloadToken<User = any>(
59
+ user: WithID<FormioSubmission<User>>,
60
+ ctx: PlatformContext
61
+ ): Promise<{user: WithID<FormioSubmission<User>>; token: FormioPayloadToken}> {
62
+ const req = ctx.getRequest();
63
+ const audit = this.formio.audit;
64
+ let form: FormioForm | null;
65
+
66
+ try {
67
+ form = (await this.db.getForm(user.form)) as any;
68
+ } catch (err) {
69
+ audit(
70
+ "EAUTH_USERFORM",
71
+ {
72
+ ...req,
73
+ userId: user._id
74
+ },
75
+ user.form,
76
+ err
77
+ );
78
+ throw err;
79
+ }
80
+
81
+ if (!form) {
82
+ audit(
83
+ "EAUTH_USERFORM",
84
+ {
85
+ ...req,
86
+ userId: user._id
87
+ },
88
+ user.form,
89
+ {message: "User form not found"}
90
+ );
91
+
92
+ throw new NotFound("User form not found.");
93
+ }
94
+
95
+ try {
96
+ user = await this.hooks.alterAsync("user", user);
97
+ } catch (err) {
98
+ // istanbul ignore next
99
+ ctx.logger.debug(err);
100
+ }
101
+
102
+ await this.hooks.alterAsync("login", user, req);
103
+
104
+ // Allow anyone to hook and modify the token.
105
+ const token = this.hooks.alter(
106
+ "token",
107
+ {
108
+ user: {
109
+ _id: user._id
110
+ },
111
+ form: {
112
+ _id: form?._id
113
+ }
114
+ },
115
+ form,
116
+ req
117
+ );
118
+
119
+ const decoded = this.hooks.alter("tokenDecode", token, req);
120
+
121
+ return {
122
+ user,
123
+ token: {
124
+ token: this.getToken(token) as string,
125
+ decoded
126
+ }
127
+ };
128
+ }
129
+
130
+ /**
131
+ * Generate session from the given authenticated user.
132
+ * @param user
133
+ * @param ctx
134
+ */
135
+ async generateSession<User = any>(user: WithID<FormioSubmission<User>>, ctx: PlatformContext) {
136
+ try {
137
+ const {user: userSession, token} = await this.generatePayloadToken(user, ctx);
138
+ this.setCurrentUser(userSession, token, ctx);
139
+
140
+ await this.currentUser(ctx.getRequest(), ctx.getResponse());
141
+ } catch (err) {
142
+ ctx.logger.error({event: "Error on OAuthActions", error: err});
143
+ throw new Unauthorized(err.message);
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Retrieve roles
149
+ * @param req
150
+ */
151
+ async getRoles(req?: Req) {
152
+ try {
153
+ const query = this.hooks.alter("roleQuery", {deleted: {$eq: null}}, req);
154
+ return await this.db.roleModel.find(query).sort({title: 1}).lean().exec();
155
+ } catch (err) {
156
+ throw new BadRequest(this.formio.util.errorCodes.role.EROLESLOAD);
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Update the role of the current user submission
162
+ * @param _id
163
+ * @param role
164
+ * @param req
165
+ */
166
+ async updateUserRole(_id: string | any, role: string, req?: Req) {
167
+ const query = this.hooks.alter(
168
+ "submissionQuery",
169
+ {
170
+ _id: this.formio.util.idToBson(_id),
171
+ deleted: {$eq: null}
172
+ },
173
+ req
174
+ );
175
+
176
+ const user = await this.db.submissionModel.findOne(query).exec();
177
+
178
+ if (!user) {
179
+ throw new BadRequest("No Submission was found with the given setting `submission`.");
180
+ }
181
+
182
+ user.roles = [this.formio.util.idToBson(role)];
183
+
184
+ if (isFunction(user.save)) {
185
+ await user.save();
186
+ }
187
+
188
+ return user;
189
+ }
190
+
191
+ /**
192
+ * Create a user submission in formio
193
+ * @param user
194
+ */
195
+ async createUser<User = any>(user: Partial<FormioSubmission<User>>) {
196
+ const submission = new this.db.submissionModel({
197
+ owner: null,
198
+ deleted: null,
199
+ roles: [],
200
+ externalsIds: [],
201
+ ...user,
202
+ created: new Date().toISOString(),
203
+ modified: new Date().toISOString()
204
+ });
205
+
206
+ user.form && (submission.form = this.db.idToBson(user.form));
207
+
208
+ await submission.save();
209
+
210
+ return submission.toObject();
211
+ }
212
+
213
+ /**
214
+ * Update user submission in formio
215
+ * @param user
216
+ */
217
+ async updateUser<User = any>(user: WithID<FormioSubmission<User>>) {
218
+ await this.db.submissionModel.updateOne(
219
+ {
220
+ _id: user._id
221
+ },
222
+ {$set: user}
223
+ );
224
+
225
+ return user;
226
+ }
227
+ }