@contember/engine-tenant-api 2.0.1 → 2.1.0-alpha.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/development/index.cjs +1 -0
- package/dist/development/index.cjs.map +1 -1
- package/dist/development/index.js +2 -1
- package/dist/development/src/model/mailing/UserMailer.cjs +18 -12
- package/dist/development/src/model/mailing/UserMailer.cjs.map +1 -1
- package/dist/development/src/model/mailing/UserMailer.js +18 -12
- package/dist/development/src/model/mailing/UserMailer.js.map +1 -1
- package/dist/development/src/model/service/InviteManager.cjs +2 -2
- package/dist/development/src/model/service/InviteManager.cjs.map +1 -1
- package/dist/development/src/model/service/InviteManager.js +2 -2
- package/dist/development/src/model/service/InviteManager.js.map +1 -1
- package/dist/development/src/utils/Mailer.cjs +29 -1
- package/dist/development/src/utils/Mailer.cjs.map +1 -1
- package/dist/development/src/utils/Mailer.js +29 -1
- package/dist/development/src/utils/Mailer.js.map +1 -1
- package/dist/production/index.cjs +1 -0
- package/dist/production/index.cjs.map +1 -1
- package/dist/production/index.js +2 -1
- package/dist/production/src/model/mailing/UserMailer.cjs +18 -12
- package/dist/production/src/model/mailing/UserMailer.cjs.map +1 -1
- package/dist/production/src/model/mailing/UserMailer.js +18 -12
- package/dist/production/src/model/mailing/UserMailer.js.map +1 -1
- package/dist/production/src/model/service/InviteManager.cjs +2 -2
- package/dist/production/src/model/service/InviteManager.cjs.map +1 -1
- package/dist/production/src/model/service/InviteManager.js +2 -2
- package/dist/production/src/model/service/InviteManager.js.map +1 -1
- package/dist/production/src/utils/Mailer.cjs +29 -1
- package/dist/production/src/utils/Mailer.cjs.map +1 -1
- package/dist/production/src/utils/Mailer.js +29 -1
- package/dist/production/src/utils/Mailer.js.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/addProjectMember.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/changeMyPassword.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/changeMyProfile.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/changePassword.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/changeProfile.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/confirmOtp.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/createApiKey.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/createResetPasswordRequest.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/createSessionToken.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/invite.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/prepareOtp.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/resetPassword.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/signIn.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/signInIdp.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/signUp.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/unmanagedInvite.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/gql/updateProjectMember.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/createApiKeySql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/createIdentitySql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/createMembershipSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/createMembershipVariableSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/createPersonSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/createPersonTokenSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/createSessionKeySql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/disableApiKeySql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/disableOneOffKeySql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getApiKeySql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getIdentityProjectsSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getIdpBySlugSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getMailTemplateSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getPersonByEmailSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getPersonByIdSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getPersonByIdentity.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getPersonByIdpSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getProjectBySlugSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/getProjectMembershipSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/patchVariablesSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/selectMembershipsSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/sqlTransaction.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/updatePesonNameSql.d.ts.map +1 -1
- package/dist/tests/cases/integration/mocked/sql/updatePesonPasswordSql.d.ts.map +1 -1
- package/dist/tests/src/dbUtils.d.ts.map +1 -1
- package/dist/tests/src/tags.d.ts.map +1 -1
- package/dist/tests/src/testGraphql.d.ts.map +1 -1
- package/dist/tests/src/testTenant.d.ts.map +1 -1
- package/dist/tests/src/testUuid.d.ts.map +1 -1
- package/dist/tests/tsconfig.tsbuildinfo +1 -1
- package/dist/types/bridges/system/IdentityFetcher.d.ts.map +1 -1
- package/dist/types/migrations/snapshot-factory.d.ts.map +1 -1
- package/dist/types/migrations/tenantCredentials.d.ts.map +1 -1
- package/dist/types/model/authorization/PermissionActions.d.ts.map +1 -1
- package/dist/types/model/mailing/UserMailer.d.ts +4 -1
- package/dist/types/model/mailing/UserMailer.d.ts.map +1 -1
- package/dist/types/model/mailing/templates/Layout.mustache.d.ts.map +1 -1
- package/dist/types/model/mailing/type.d.ts.map +1 -1
- package/dist/types/model/queries/idp/helpers.d.ts.map +1 -1
- package/dist/types/model/queries/membership/ProjectSlugSpecification.d.ts.map +1 -1
- package/dist/types/model/service/helpers/getPreferredProject.d.ts.map +1 -1
- package/dist/types/model/service/idp/providers/OIDCHelpers.d.ts.map +1 -1
- package/dist/types/model/service/idp/providers/helpers.d.ts.map +1 -1
- package/dist/types/model/service/membershipUtils.d.ts.map +1 -1
- package/dist/types/model/utils/email.d.ts.map +1 -1
- package/dist/types/model/utils/password.d.ts.map +1 -1
- package/dist/types/model/utils/time.d.ts.map +1 -1
- package/dist/types/model/utils/tokenUtils.d.ts.map +1 -1
- package/dist/types/model/utils/typeUtils.d.ts.map +1 -1
- package/dist/types/resolvers/TenantResolverContextFactory.d.ts.map +1 -1
- package/dist/types/resolvers/errorUtils.d.ts.map +1 -1
- package/dist/types/resolvers/membershipUtils.d.ts.map +1 -1
- package/dist/types/tsconfig.tsbuildinfo +1 -1
- package/dist/types/utils/Mailer.d.ts +24 -4
- package/dist/types/utils/Mailer.d.ts.map +1 -1
- package/dist/types/utils/array.d.ts.map +1 -1
- package/dist/types/utils/batchQuery.d.ts +1 -1
- package/dist/types/utils/batchQuery.d.ts.map +1 -1
- package/package.json +13 -13
- package/src/model/mailing/UserMailer.ts +23 -15
- package/src/model/service/InviteManager.ts +2 -2
- package/src/utils/Mailer.ts +56 -6
|
@@ -289,6 +289,7 @@ exports.ResolverFactory = ResolverFactory.ResolverFactory;
|
|
|
289
289
|
exports.TenantResolverContextFactory = TenantResolverContextFactory.TenantResolverContextFactory;
|
|
290
290
|
exports.createResolverContext = TenantResolverContextFactory.createResolverContext;
|
|
291
291
|
exports.NodeMailer = Mailer.NodeMailer;
|
|
292
|
+
exports.WebhookMailer = Mailer.WebhookMailer;
|
|
292
293
|
exports.createMailer = Mailer.createMailer;
|
|
293
294
|
exports.TemplateRenderer = TemplateRenderer.TemplateRenderer;
|
|
294
295
|
exports.TenantMigrationsRunner = runner.TenantMigrationsRunner;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -126,7 +126,7 @@ import { IdentityTypeResolver } from "./src/resolvers/types/IdentityTypeResolver
|
|
|
126
126
|
import { ProjectTypeResolver } from "./src/resolvers/types/ProjectTypeResolver.js";
|
|
127
127
|
import { ResolverFactory } from "./src/resolvers/ResolverFactory.js";
|
|
128
128
|
import { TenantResolverContextFactory, createResolverContext } from "./src/resolvers/TenantResolverContextFactory.js";
|
|
129
|
-
import { NodeMailer, createMailer } from "./src/utils/Mailer.js";
|
|
129
|
+
import { NodeMailer, WebhookMailer, createMailer } from "./src/utils/Mailer.js";
|
|
130
130
|
import { TemplateRenderer } from "./src/utils/TemplateRenderer.js";
|
|
131
131
|
import { TenantMigrationsRunner } from "./src/migrations/runner.js";
|
|
132
132
|
import { default as default2 } from "./src/schema/tenant.graphql.js";
|
|
@@ -279,6 +279,7 @@ export {
|
|
|
279
279
|
UserMailer,
|
|
280
280
|
VerifyErrorCode,
|
|
281
281
|
VerifyResult,
|
|
282
|
+
WebhookMailer,
|
|
282
283
|
computeTokenHash,
|
|
283
284
|
createMailer,
|
|
284
285
|
createResolverContext,
|
|
@@ -20,43 +20,49 @@ class UserMailer {
|
|
|
20
20
|
this.templateRenderer = templateRenderer;
|
|
21
21
|
}
|
|
22
22
|
async sendNewUserInvitedMail(dbContext, mailArguments, customMailOptions) {
|
|
23
|
-
const
|
|
23
|
+
const templateId = { type: type.MailType.newUserInvited, ...customMailOptions };
|
|
24
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
24
25
|
subject: "You have been invited to {{project}}",
|
|
25
26
|
content: NewUserInvited_mustache,
|
|
26
27
|
replyTo: null
|
|
27
28
|
};
|
|
28
|
-
await this.
|
|
29
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
29
30
|
}
|
|
30
31
|
async sendExistingUserInvitedEmail(dbContext, mailArguments, customMailOptions) {
|
|
31
|
-
const
|
|
32
|
+
const templateId = { type: type.MailType.existingUserInvited, ...customMailOptions };
|
|
33
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
32
34
|
subject: "You have been invited to {{project}}",
|
|
33
35
|
content: ExistingUserInvited_mustache,
|
|
34
36
|
replyTo: null
|
|
35
37
|
};
|
|
36
|
-
await this.
|
|
38
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
37
39
|
}
|
|
38
40
|
async sendPasswordResetEmail(dbContext, mailArguments, customMailOptions) {
|
|
39
|
-
const
|
|
41
|
+
const templateId = { type: type.MailType.passwordReset, ...customMailOptions };
|
|
42
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
40
43
|
subject: "Password reset",
|
|
41
44
|
content: PasswordReset_mustache,
|
|
42
45
|
replyTo: null
|
|
43
46
|
};
|
|
44
|
-
await this.
|
|
47
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
45
48
|
}
|
|
46
49
|
async sendPasswordlessEmail(dbContext, mailArguments, customMailOptions) {
|
|
47
|
-
const
|
|
50
|
+
const templateId = { type: type.MailType.passwordlessSignIn, ...customMailOptions };
|
|
51
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
48
52
|
subject: "Sign in here",
|
|
49
53
|
content: PasswordlessSignIn_mustache,
|
|
50
54
|
replyTo: null
|
|
51
55
|
};
|
|
52
|
-
await this.
|
|
56
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
53
57
|
}
|
|
54
|
-
async
|
|
55
|
-
const html = await this.templateRenderer.render(template.content,
|
|
58
|
+
async sendMail(templateId, template, variables) {
|
|
59
|
+
const html = await this.templateRenderer.render(template.content, variables);
|
|
56
60
|
await this.mailer.send({
|
|
57
|
-
to:
|
|
58
|
-
subject: await this.templateRenderer.render(template.subject,
|
|
61
|
+
to: variables.email,
|
|
62
|
+
subject: await this.templateRenderer.render(template.subject, variables),
|
|
59
63
|
html,
|
|
64
|
+
variables,
|
|
65
|
+
template: templateId,
|
|
60
66
|
...template.replyTo ? {
|
|
61
67
|
replyTo: template.replyTo
|
|
62
68
|
} : {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserMailer.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/mailing/UserMailer.ts"],"sourcesContent":["import { Mailer, TemplateRenderer } from '../../utils'\nimport NewUserInvited from './templates/NewUserInvited.mustache'\nimport ExistingUserInvited from './templates/ExistingUserInvited.mustache'\nimport PasswordReset from './templates/PasswordReset.mustache'\nimport PasswordlessSignIn from './templates/PasswordlessSignIn.mustache'\nimport { MailTemplateData, MailTemplateIdentifier, MailType } from './type'\nimport { MailTemplateQuery } from '../queries'\nimport Layout from './templates/Layout.mustache'\nimport { DatabaseContext } from '../utils'\n\nexport class UserMailer {\n\tconstructor(\n\t\tprivate readonly mailer: Mailer,\n\t\tprivate readonly templateRenderer: TemplateRenderer,\n\t) { }\n\n\tasync sendNewUserInvitedMail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; password: string | null; token: string | null; project: string; projectSlug: string },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst
|
|
1
|
+
{"version":3,"file":"UserMailer.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/mailing/UserMailer.ts"],"sourcesContent":["import { Mailer, TemplateRenderer } from '../../utils'\nimport NewUserInvited from './templates/NewUserInvited.mustache'\nimport ExistingUserInvited from './templates/ExistingUserInvited.mustache'\nimport PasswordReset from './templates/PasswordReset.mustache'\nimport PasswordlessSignIn from './templates/PasswordlessSignIn.mustache'\nimport { MailTemplateData, MailTemplateIdentifier, MailType } from './type'\nimport { MailTemplateQuery } from '../queries'\nimport Layout from './templates/Layout.mustache'\nimport { DatabaseContext } from '../utils'\nimport { Acl } from '@contember/schema'\n\nexport class UserMailer {\n\tconstructor(\n\t\tprivate readonly mailer: Mailer,\n\t\tprivate readonly templateRenderer: TemplateRenderer,\n\t) { }\n\n\tasync sendNewUserInvitedMail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; password: string | null; token: string | null; project: string; projectSlug: string; memberships: readonly Acl.Membership[]},\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.newUserInvited, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: NewUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendExistingUserInvitedEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; project: string; projectSlug: string; memberships: readonly Acl.Membership[] },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.existingUserInvited, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: ExistingUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendPasswordResetEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.passwordReset, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'Password reset',\n\t\t\tcontent: PasswordReset,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendPasswordlessEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string; url?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.passwordlessSignIn, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'Sign in here',\n\t\t\tcontent: PasswordlessSignIn,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tprivate async sendMail(\n\t\ttemplateId: MailTemplateIdentifier,\n\t\ttemplate: Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'>,\n\t\tvariables: Record<string, any>,\n\t) {\n\t\tconst html = await this.templateRenderer.render(template.content, variables)\n\t\tawait this.mailer.send({\n\t\t\tto: variables.email,\n\t\t\tsubject: await this.templateRenderer.render(template.subject, variables),\n\t\t\thtml,\n\t\t\tvariables,\n\t\t\ttemplate: templateId,\n\t\t\t...(template.replyTo ? {\n\t\t\t\treplyTo: template.replyTo,\n\t\t\t} : {}),\n\t\t})\n\t}\n\n\tprivate async getCustomTemplate(\n\t\tdbContext: DatabaseContext,\n\t\tidentifier: MailTemplateIdentifier,\n\t): Promise<Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'> | null> {\n\t\tconst customTemplate =\n\t\t\t(await dbContext.queryHandler.fetch(new MailTemplateQuery(identifier)))\n\t\t\t?? (await dbContext.queryHandler.fetch(new MailTemplateQuery({ ...identifier, projectId: null })))\n\n\t\tif (!customTemplate) {\n\t\t\treturn null\n\t\t}\n\t\tconst content = customTemplate.useLayout ? Layout(customTemplate.content) : customTemplate.content\n\t\treturn { content, subject: customTemplate.subject, replyTo: customTemplate.replyTo }\n\t}\n}\n"],"names":["MailType","NewUserInvited","ExistingUserInvited","PasswordReset","PasswordlessSignIn","MailTemplateQuery","Layout"],"mappings":";;;;;;;;;;;;;;;;AAWO,MAAM,WAAW;AAAA,EACvB,YACkB,QACA,kBAChB;AAFgB,SAAA,SAAA;AACA,SAAA,mBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAMA,KAAS,SAAA,gBAAgB,GAAG,kBAAkB;AACzE,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAASC;AAAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,6BACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAMD,KAAS,SAAA,qBAAqB,GAAG,kBAAkB;AAC9E,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAASE;AAAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAMF,KAAS,SAAA,eAAe,GAAG,kBAAkB;AACxE,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAASG;AAAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,sBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAMH,KAAS,SAAA,oBAAoB,GAAG,kBAAkB;AAC7E,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAASI;AAAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAc,SACb,YACA,UACA,WACC;AACD,UAAM,OAAO,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,SAAS;AACrE,UAAA,KAAK,OAAO,KAAK;AAAA,MACtB,IAAI,UAAU;AAAA,MACd,SAAS,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,SAAS;AAAA,MACvE;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,GAAI,SAAS,UAAU;AAAA,QACtB,SAAS,SAAS;AAAA,MAAA,IACf,CAAA;AAAA,IAAC,CACL;AAAA,EAAA;AAAA,EAGF,MAAc,kBACb,WACA,YAC4E;AACtE,UAAA,iBACJ,MAAM,UAAU,aAAa,MAAM,IAAIC,kBAAA,kBAAkB,UAAU,CAAC,KACjE,MAAM,UAAU,aAAa,MAAM,IAAIA,oCAAkB,EAAE,GAAG,YAAY,WAAW,KAAK,CAAC,CAAC;AAEjG,QAAI,CAAC,gBAAgB;AACb,aAAA;AAAA,IAAA;AAER,UAAM,UAAU,eAAe,YAAYC,gBAAO,eAAe,OAAO,IAAI,eAAe;AAC3F,WAAO,EAAE,SAAS,SAAS,eAAe,SAAS,SAAS,eAAe,QAAQ;AAAA,EAAA;AAErF;;"}
|
|
@@ -18,43 +18,49 @@ class UserMailer {
|
|
|
18
18
|
this.templateRenderer = templateRenderer;
|
|
19
19
|
}
|
|
20
20
|
async sendNewUserInvitedMail(dbContext, mailArguments, customMailOptions) {
|
|
21
|
-
const
|
|
21
|
+
const templateId = { type: MailType.newUserInvited, ...customMailOptions };
|
|
22
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
22
23
|
subject: "You have been invited to {{project}}",
|
|
23
24
|
content: NewUserInvited,
|
|
24
25
|
replyTo: null
|
|
25
26
|
};
|
|
26
|
-
await this.
|
|
27
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
27
28
|
}
|
|
28
29
|
async sendExistingUserInvitedEmail(dbContext, mailArguments, customMailOptions) {
|
|
29
|
-
const
|
|
30
|
+
const templateId = { type: MailType.existingUserInvited, ...customMailOptions };
|
|
31
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
30
32
|
subject: "You have been invited to {{project}}",
|
|
31
33
|
content: ExistingUserInvited,
|
|
32
34
|
replyTo: null
|
|
33
35
|
};
|
|
34
|
-
await this.
|
|
36
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
35
37
|
}
|
|
36
38
|
async sendPasswordResetEmail(dbContext, mailArguments, customMailOptions) {
|
|
37
|
-
const
|
|
39
|
+
const templateId = { type: MailType.passwordReset, ...customMailOptions };
|
|
40
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
38
41
|
subject: "Password reset",
|
|
39
42
|
content: PasswordReset,
|
|
40
43
|
replyTo: null
|
|
41
44
|
};
|
|
42
|
-
await this.
|
|
45
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
43
46
|
}
|
|
44
47
|
async sendPasswordlessEmail(dbContext, mailArguments, customMailOptions) {
|
|
45
|
-
const
|
|
48
|
+
const templateId = { type: MailType.passwordlessSignIn, ...customMailOptions };
|
|
49
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
46
50
|
subject: "Sign in here",
|
|
47
51
|
content: PasswordlessSignIn,
|
|
48
52
|
replyTo: null
|
|
49
53
|
};
|
|
50
|
-
await this.
|
|
54
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
51
55
|
}
|
|
52
|
-
async
|
|
53
|
-
const html = await this.templateRenderer.render(template.content,
|
|
56
|
+
async sendMail(templateId, template, variables) {
|
|
57
|
+
const html = await this.templateRenderer.render(template.content, variables);
|
|
54
58
|
await this.mailer.send({
|
|
55
|
-
to:
|
|
56
|
-
subject: await this.templateRenderer.render(template.subject,
|
|
59
|
+
to: variables.email,
|
|
60
|
+
subject: await this.templateRenderer.render(template.subject, variables),
|
|
57
61
|
html,
|
|
62
|
+
variables,
|
|
63
|
+
template: templateId,
|
|
58
64
|
...template.replyTo ? {
|
|
59
65
|
replyTo: template.replyTo
|
|
60
66
|
} : {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserMailer.js","sources":["../../../../../../packages/engine-tenant-api/src/model/mailing/UserMailer.ts"],"sourcesContent":["import { Mailer, TemplateRenderer } from '../../utils'\nimport NewUserInvited from './templates/NewUserInvited.mustache'\nimport ExistingUserInvited from './templates/ExistingUserInvited.mustache'\nimport PasswordReset from './templates/PasswordReset.mustache'\nimport PasswordlessSignIn from './templates/PasswordlessSignIn.mustache'\nimport { MailTemplateData, MailTemplateIdentifier, MailType } from './type'\nimport { MailTemplateQuery } from '../queries'\nimport Layout from './templates/Layout.mustache'\nimport { DatabaseContext } from '../utils'\n\nexport class UserMailer {\n\tconstructor(\n\t\tprivate readonly mailer: Mailer,\n\t\tprivate readonly templateRenderer: TemplateRenderer,\n\t) { }\n\n\tasync sendNewUserInvitedMail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; password: string | null; token: string | null; project: string; projectSlug: string },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst
|
|
1
|
+
{"version":3,"file":"UserMailer.js","sources":["../../../../../../packages/engine-tenant-api/src/model/mailing/UserMailer.ts"],"sourcesContent":["import { Mailer, TemplateRenderer } from '../../utils'\nimport NewUserInvited from './templates/NewUserInvited.mustache'\nimport ExistingUserInvited from './templates/ExistingUserInvited.mustache'\nimport PasswordReset from './templates/PasswordReset.mustache'\nimport PasswordlessSignIn from './templates/PasswordlessSignIn.mustache'\nimport { MailTemplateData, MailTemplateIdentifier, MailType } from './type'\nimport { MailTemplateQuery } from '../queries'\nimport Layout from './templates/Layout.mustache'\nimport { DatabaseContext } from '../utils'\nimport { Acl } from '@contember/schema'\n\nexport class UserMailer {\n\tconstructor(\n\t\tprivate readonly mailer: Mailer,\n\t\tprivate readonly templateRenderer: TemplateRenderer,\n\t) { }\n\n\tasync sendNewUserInvitedMail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; password: string | null; token: string | null; project: string; projectSlug: string; memberships: readonly Acl.Membership[]},\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.newUserInvited, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: NewUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendExistingUserInvitedEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; project: string; projectSlug: string; memberships: readonly Acl.Membership[] },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.existingUserInvited, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: ExistingUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendPasswordResetEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.passwordReset, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'Password reset',\n\t\t\tcontent: PasswordReset,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendPasswordlessEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string; url?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.passwordlessSignIn, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'Sign in here',\n\t\t\tcontent: PasswordlessSignIn,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tprivate async sendMail(\n\t\ttemplateId: MailTemplateIdentifier,\n\t\ttemplate: Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'>,\n\t\tvariables: Record<string, any>,\n\t) {\n\t\tconst html = await this.templateRenderer.render(template.content, variables)\n\t\tawait this.mailer.send({\n\t\t\tto: variables.email,\n\t\t\tsubject: await this.templateRenderer.render(template.subject, variables),\n\t\t\thtml,\n\t\t\tvariables,\n\t\t\ttemplate: templateId,\n\t\t\t...(template.replyTo ? {\n\t\t\t\treplyTo: template.replyTo,\n\t\t\t} : {}),\n\t\t})\n\t}\n\n\tprivate async getCustomTemplate(\n\t\tdbContext: DatabaseContext,\n\t\tidentifier: MailTemplateIdentifier,\n\t): Promise<Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'> | null> {\n\t\tconst customTemplate =\n\t\t\t(await dbContext.queryHandler.fetch(new MailTemplateQuery(identifier)))\n\t\t\t?? (await dbContext.queryHandler.fetch(new MailTemplateQuery({ ...identifier, projectId: null })))\n\n\t\tif (!customTemplate) {\n\t\t\treturn null\n\t\t}\n\t\tconst content = customTemplate.useLayout ? Layout(customTemplate.content) : customTemplate.content\n\t\treturn { content, subject: customTemplate.subject, replyTo: customTemplate.replyTo }\n\t}\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AAWO,MAAM,WAAW;AAAA,EACvB,YACkB,QACA,kBAChB;AAFgB,SAAA,SAAA;AACA,SAAA,mBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAM,SAAS,gBAAgB,GAAG,kBAAkB;AACzE,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,6BACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAM,SAAS,qBAAqB,GAAG,kBAAkB;AAC9E,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAM,SAAS,eAAe,GAAG,kBAAkB;AACxE,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,sBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAM,SAAS,oBAAoB,GAAG,kBAAkB;AAC7E,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAAS;AAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAc,SACb,YACA,UACA,WACC;AACD,UAAM,OAAO,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,SAAS;AACrE,UAAA,KAAK,OAAO,KAAK;AAAA,MACtB,IAAI,UAAU;AAAA,MACd,SAAS,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,SAAS;AAAA,MACvE;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,GAAI,SAAS,UAAU;AAAA,QACtB,SAAS,SAAS;AAAA,MAAA,IACf,CAAA;AAAA,IAAC,CACL;AAAA,EAAA;AAAA,EAGF,MAAc,kBACb,WACA,YAC4E;AACtE,UAAA,iBACJ,MAAM,UAAU,aAAa,MAAM,IAAI,kBAAkB,UAAU,CAAC,KACjE,MAAM,UAAU,aAAa,MAAM,IAAI,kBAAkB,EAAE,GAAG,YAAY,WAAW,KAAK,CAAC,CAAC;AAEjG,QAAI,CAAC,gBAAgB;AACb,aAAA;AAAA,IAAA;AAER,UAAM,UAAU,eAAe,YAAY,OAAO,eAAe,OAAO,IAAI,eAAe;AAC3F,WAAO,EAAE,SAAS,SAAS,eAAe,SAAS,SAAS,eAAe,QAAQ;AAAA,EAAA;AAErF;"}
|
|
@@ -75,11 +75,11 @@ class InviteManager {
|
|
|
75
75
|
if (isNew) {
|
|
76
76
|
await this.mailer.sendNewUserInvitedMail(
|
|
77
77
|
trx,
|
|
78
|
-
{ email: email$1, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken },
|
|
78
|
+
{ email: email$1, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken, memberships },
|
|
79
79
|
customMailOptions
|
|
80
80
|
);
|
|
81
81
|
} else {
|
|
82
|
-
await this.mailer.sendExistingUserInvitedEmail(trx, { email: email$1, project: project.name, projectSlug: project.slug }, customMailOptions);
|
|
82
|
+
await this.mailer.sendExistingUserInvitedEmail(trx, { email: email$1, project: project.name, projectSlug: project.slug, memberships }, customMailOptions);
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
return new Response.ResponseOk(new InviteResult(person, isNew));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InviteManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/InviteManager.ts"],"sourcesContent":["import {\n\tCreateIdentityCommand,\n\tCreateOrUpdateProjectMembershipCommand,\n\tCreatePersonCommand,\n\tCreatePersonTokenCommand,\n\tSavePersonTokenCommand,\n} from '../commands'\nimport { Providers } from '../providers'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { Project, ProjectSchemaResolver } from '../type'\nimport { InviteErrorCode, InviteMethod } from '../../schema'\nimport { TenantRole } from '../authorization'\nimport { UserMailer } from '../mailing'\nimport { createAppendMembershipVariables } from './membershipUtils'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DatabaseContext, TokenHash } from '../utils'\nimport { NoPassword, PasswordPlain } from '../dtos'\nimport { Acl } from '@contember/schema'\nimport { validateEmail } from '../utils/email'\nimport { INVITATION_RESET_TOKEN_EXPIRATION_MINUTES } from '../consts/expirations'\n\nexport interface InviteData {\n\temail: string\n\tname?: string\n\tproject: Project\n\tmemberships: readonly Acl.Membership[]\n\tnoEmail?: boolean\n\tpassword?: string\n\temailVariant?: string\n\tmethod?: InviteMethod\n\tpasswordResetTokenHash?: TokenHash\n}\n\nexport class InviteManager {\n\tconstructor(\n\t\tprivate readonly providers: Providers,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectSchemaResolver: ProjectSchemaResolver,\n\t) { }\n\n\tasync invite(\n\t\tdbContext: DatabaseContext,\n\t\t{\n\t\t\temail,\n\t\t\tproject,\n\t\t\tname,\n\t\t\tmemberships,\n\t\t\tmethod,\n\t\t\temailVariant,\n\t\t\tnoEmail = false,\n\t\t\tpassword,\n\t\t\tpasswordResetTokenHash,\n\t\t}: InviteData,\n\t): Promise<InviteResponse> {\n\t\tif (!validateEmail(email.trim())) {\n\t\t\treturn new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')\n\t\t}\n\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tlet person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tconst isNew = !person\n\t\t\tlet generatedPassword: string | null = null\n\t\t\tlet resetToken: string | null = null\n\t\t\tif (!person) {\n\t\t\t\tconst identityId = await trx.commandBus.execute(new CreateIdentityCommand([TenantRole.PERSON]))\n\n\t\t\t\tgeneratedPassword = password ??\n\t\t\t\t\t(method === 'CREATE_PASSWORD' ? (await this.providers.randomBytes(9)).toString('base64') : null)\n\t\t\t\tconst passwordWrapper = generatedPassword !== null ? new PasswordPlain(generatedPassword) : NoPassword\n\n\t\t\t\tperson = await trx.commandBus.execute(new CreatePersonCommand({\n\t\t\t\t\tidentityId,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\tpassword: passwordWrapper,\n\t\t\t\t}))\n\t\t\t\tif (method === 'RESET_PASSWORD') {\n\t\t\t\t\tconst result = await trx.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id, await this.getInviteExpiration(project.slug)))\n\t\t\t\t\tresetToken = result.token\n\t\t\t\t}\n\t\t\t\tif (passwordResetTokenHash) {\n\t\t\t\t\tawait trx.commandBus.execute(new SavePersonTokenCommand(person.id, passwordResetTokenHash, 'password_reset', await this.getInviteExpiration(project.slug)))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const membershipUpdate of createAppendMembershipVariables(memberships)) {\n\t\t\t\tawait trx.commandBus.execute(new CreateOrUpdateProjectMembershipCommand(project.id, person.identity_id, membershipUpdate))\n\t\t\t}\n\t\t\tif (!noEmail) {\n\t\t\t\tconst customMailOptions = {\n\t\t\t\t\tprojectId: project.id,\n\t\t\t\t\tvariant: emailVariant ?? '',\n\t\t\t\t}\n\t\t\t\tif (isNew) {\n\t\t\t\t\tawait this.mailer.sendNewUserInvitedMail(\n\t\t\t\t\t\ttrx,\n\t\t\t\t\t\t{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken },\n\t\t\t\t\t\tcustomMailOptions,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug }, customMailOptions)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn new ResponseOk(new InviteResult(person, isNew))\n\t\t})\n\t}\n\n\tprivate async getInviteExpiration(projectSlug: string): Promise<number> {\n\t\tconst schema = await this.projectSchemaResolver.getSchema(projectSlug)\n\n\t\treturn schema?.settings.tenant?.inviteExpirationMinutes ?? INVITATION_RESET_TOKEN_EXPIRATION_MINUTES\n\t}\n}\n\nexport type InviteResponse = Response<InviteResult, InviteErrorCode>\n\nexport class InviteResult {\n\tconstructor(public readonly person: Omit<PersonRow, 'roles'>, public readonly isNew: boolean) { }\n}\n"],"names":["email","validateEmail","ResponseError","PersonQuery","CreateIdentityCommand","TenantRole","PasswordPlain","NoPassword","CreatePersonCommand","CreatePersonTokenCommand","SavePersonTokenCommand","createAppendMembershipVariables","CreateOrUpdateProjectMembershipCommand","ResponseOk","INVITATION_RESET_TOKEN_EXPIRATION_MINUTES"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCO,MAAM,cAAc;AAAA,EAC1B,YACkB,WACA,QACA,uBAChB;AAHgB,SAAA,YAAA;AACA,SAAA,SAAA;AACA,SAAA,wBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,OACL,WACA;AAAA,IAAA,OACCA;AAAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,GAEyB;AAC1B,QAAI,CAACC,MAAA,cAAcD,QAAM,KAAM,CAAA,GAAG;AAC1B,aAAA,IAAIE,SAAAA,cAAc,wBAAwB,yCAAyC;AAAA,IAAA;AAG3F,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC3C,UAAA,SAA0C,MAAM,IAAI,aAAa,MAAMC,wBAAY,QAAQH,OAAK,CAAC;AACrG,YAAM,QAAQ,CAAC;AACf,UAAI,oBAAmC;AACvC,UAAI,aAA4B;AAChC,UAAI,CAAC,QAAQ;AACN,cAAA,aAAa,MAAM,IAAI,WAAW,QAAQ,IAAII,sBAAAA,sBAAsB,CAACC,MAAAA,WAAW,MAAM,CAAC,CAAC;AAE1E,4BAAA,aAClB,WAAW,qBAAqB,MAAM,KAAK,UAAU,YAAY,CAAC,GAAG,SAAS,QAAQ,IAAI;AAC5F,cAAM,kBAAkB,sBAAsB,OAAO,IAAIC,SAAA,cAAc,iBAAiB,IAAIC,SAAA;AAE5F,iBAAS,MAAM,IAAI,WAAW,QAAQ,IAAIC,oBAAAA,oBAAoB;AAAA,UAC7D;AAAA,UAAA,OACAR;AAAAA,UACA;AAAA,UACA,UAAU;AAAA,QAAA,CACV,CAAC;AACF,YAAI,WAAW,kBAAkB;AAChC,gBAAM,SAAS,MAAM,IAAI,WAAW,QAAQS,yBAAyB,yBAAA,2BAA2B,OAAO,IAAI,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AACxJ,uBAAa,OAAO;AAAA,QAAA;AAErB,YAAI,wBAAwB;AAC3B,gBAAM,IAAI,WAAW,QAAQ,IAAIC,8CAAuB,OAAO,IAAI,wBAAwB,kBAAkB,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AAAA,QAAA;AAAA,MAC3J;AAEU,iBAAA,oBAAoBC,gDAAgC,WAAW,GAAG;AACtE,cAAA,IAAI,WAAW,QAAQ,IAAIC,uCAAA,uCAAuC,QAAQ,IAAI,OAAO,aAAa,gBAAgB,CAAC;AAAA,MAAA;AAE1H,UAAI,CAAC,SAAS;AACb,cAAM,oBAAoB;AAAA,UACzB,WAAW,QAAQ;AAAA,UACnB,SAAS,gBAAgB;AAAA,QAC1B;AACA,YAAI,OAAO;AACV,gBAAM,KAAK,OAAO;AAAA,YACjB;AAAA,YACA,EAAA,OAAEZ,SAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU,mBAAmB,OAAO,
|
|
1
|
+
{"version":3,"file":"InviteManager.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/service/InviteManager.ts"],"sourcesContent":["import {\n\tCreateIdentityCommand,\n\tCreateOrUpdateProjectMembershipCommand,\n\tCreatePersonCommand,\n\tCreatePersonTokenCommand,\n\tSavePersonTokenCommand,\n} from '../commands'\nimport { Providers } from '../providers'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { Project, ProjectSchemaResolver } from '../type'\nimport { InviteErrorCode, InviteMethod } from '../../schema'\nimport { TenantRole } from '../authorization'\nimport { UserMailer } from '../mailing'\nimport { createAppendMembershipVariables } from './membershipUtils'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DatabaseContext, TokenHash } from '../utils'\nimport { NoPassword, PasswordPlain } from '../dtos'\nimport { Acl } from '@contember/schema'\nimport { validateEmail } from '../utils/email'\nimport { INVITATION_RESET_TOKEN_EXPIRATION_MINUTES } from '../consts/expirations'\n\nexport interface InviteData {\n\temail: string\n\tname?: string\n\tproject: Project\n\tmemberships: readonly Acl.Membership[]\n\tnoEmail?: boolean\n\tpassword?: string\n\temailVariant?: string\n\tmethod?: InviteMethod\n\tpasswordResetTokenHash?: TokenHash\n}\n\nexport class InviteManager {\n\tconstructor(\n\t\tprivate readonly providers: Providers,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectSchemaResolver: ProjectSchemaResolver,\n\t) { }\n\n\tasync invite(\n\t\tdbContext: DatabaseContext,\n\t\t{\n\t\t\temail,\n\t\t\tproject,\n\t\t\tname,\n\t\t\tmemberships,\n\t\t\tmethod,\n\t\t\temailVariant,\n\t\t\tnoEmail = false,\n\t\t\tpassword,\n\t\t\tpasswordResetTokenHash,\n\t\t}: InviteData,\n\t): Promise<InviteResponse> {\n\t\tif (!validateEmail(email.trim())) {\n\t\t\treturn new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')\n\t\t}\n\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tlet person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tconst isNew = !person\n\t\t\tlet generatedPassword: string | null = null\n\t\t\tlet resetToken: string | null = null\n\t\t\tif (!person) {\n\t\t\t\tconst identityId = await trx.commandBus.execute(new CreateIdentityCommand([TenantRole.PERSON]))\n\n\t\t\t\tgeneratedPassword = password ??\n\t\t\t\t\t(method === 'CREATE_PASSWORD' ? (await this.providers.randomBytes(9)).toString('base64') : null)\n\t\t\t\tconst passwordWrapper = generatedPassword !== null ? new PasswordPlain(generatedPassword) : NoPassword\n\n\t\t\t\tperson = await trx.commandBus.execute(new CreatePersonCommand({\n\t\t\t\t\tidentityId,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\tpassword: passwordWrapper,\n\t\t\t\t}))\n\t\t\t\tif (method === 'RESET_PASSWORD') {\n\t\t\t\t\tconst result = await trx.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id, await this.getInviteExpiration(project.slug)))\n\t\t\t\t\tresetToken = result.token\n\t\t\t\t}\n\t\t\t\tif (passwordResetTokenHash) {\n\t\t\t\t\tawait trx.commandBus.execute(new SavePersonTokenCommand(person.id, passwordResetTokenHash, 'password_reset', await this.getInviteExpiration(project.slug)))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const membershipUpdate of createAppendMembershipVariables(memberships)) {\n\t\t\t\tawait trx.commandBus.execute(new CreateOrUpdateProjectMembershipCommand(project.id, person.identity_id, membershipUpdate))\n\t\t\t}\n\t\t\tif (!noEmail) {\n\t\t\t\tconst customMailOptions = {\n\t\t\t\t\tprojectId: project.id,\n\t\t\t\t\tvariant: emailVariant ?? '',\n\t\t\t\t}\n\t\t\t\tif (isNew) {\n\t\t\t\t\tawait this.mailer.sendNewUserInvitedMail(\n\t\t\t\t\t\ttrx,\n\t\t\t\t\t\t{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken, memberships },\n\t\t\t\t\t\tcustomMailOptions,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug, memberships }, customMailOptions)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn new ResponseOk(new InviteResult(person, isNew))\n\t\t})\n\t}\n\n\tprivate async getInviteExpiration(projectSlug: string): Promise<number> {\n\t\tconst schema = await this.projectSchemaResolver.getSchema(projectSlug)\n\n\t\treturn schema?.settings.tenant?.inviteExpirationMinutes ?? INVITATION_RESET_TOKEN_EXPIRATION_MINUTES\n\t}\n}\n\nexport type InviteResponse = Response<InviteResult, InviteErrorCode>\n\nexport class InviteResult {\n\tconstructor(public readonly person: Omit<PersonRow, 'roles'>, public readonly isNew: boolean) { }\n}\n"],"names":["email","validateEmail","ResponseError","PersonQuery","CreateIdentityCommand","TenantRole","PasswordPlain","NoPassword","CreatePersonCommand","CreatePersonTokenCommand","SavePersonTokenCommand","createAppendMembershipVariables","CreateOrUpdateProjectMembershipCommand","ResponseOk","INVITATION_RESET_TOKEN_EXPIRATION_MINUTES"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAiCO,MAAM,cAAc;AAAA,EAC1B,YACkB,WACA,QACA,uBAChB;AAHgB,SAAA,YAAA;AACA,SAAA,SAAA;AACA,SAAA,wBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,OACL,WACA;AAAA,IAAA,OACCA;AAAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,GAEyB;AAC1B,QAAI,CAACC,MAAA,cAAcD,QAAM,KAAM,CAAA,GAAG;AAC1B,aAAA,IAAIE,SAAAA,cAAc,wBAAwB,yCAAyC;AAAA,IAAA;AAG3F,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC3C,UAAA,SAA0C,MAAM,IAAI,aAAa,MAAMC,wBAAY,QAAQH,OAAK,CAAC;AACrG,YAAM,QAAQ,CAAC;AACf,UAAI,oBAAmC;AACvC,UAAI,aAA4B;AAChC,UAAI,CAAC,QAAQ;AACN,cAAA,aAAa,MAAM,IAAI,WAAW,QAAQ,IAAII,sBAAAA,sBAAsB,CAACC,MAAAA,WAAW,MAAM,CAAC,CAAC;AAE1E,4BAAA,aAClB,WAAW,qBAAqB,MAAM,KAAK,UAAU,YAAY,CAAC,GAAG,SAAS,QAAQ,IAAI;AAC5F,cAAM,kBAAkB,sBAAsB,OAAO,IAAIC,SAAA,cAAc,iBAAiB,IAAIC,SAAA;AAE5F,iBAAS,MAAM,IAAI,WAAW,QAAQ,IAAIC,oBAAAA,oBAAoB;AAAA,UAC7D;AAAA,UAAA,OACAR;AAAAA,UACA;AAAA,UACA,UAAU;AAAA,QAAA,CACV,CAAC;AACF,YAAI,WAAW,kBAAkB;AAChC,gBAAM,SAAS,MAAM,IAAI,WAAW,QAAQS,yBAAyB,yBAAA,2BAA2B,OAAO,IAAI,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AACxJ,uBAAa,OAAO;AAAA,QAAA;AAErB,YAAI,wBAAwB;AAC3B,gBAAM,IAAI,WAAW,QAAQ,IAAIC,8CAAuB,OAAO,IAAI,wBAAwB,kBAAkB,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AAAA,QAAA;AAAA,MAC3J;AAEU,iBAAA,oBAAoBC,gDAAgC,WAAW,GAAG;AACtE,cAAA,IAAI,WAAW,QAAQ,IAAIC,uCAAA,uCAAuC,QAAQ,IAAI,OAAO,aAAa,gBAAgB,CAAC;AAAA,MAAA;AAE1H,UAAI,CAAC,SAAS;AACb,cAAM,oBAAoB;AAAA,UACzB,WAAW,QAAQ;AAAA,UACnB,SAAS,gBAAgB;AAAA,QAC1B;AACA,YAAI,OAAO;AACV,gBAAM,KAAK,OAAO;AAAA,YACjB;AAAA,YACA,EAAA,OAAEZ,SAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU,mBAAmB,OAAO,YAAY,YAAY;AAAA,YACvH;AAAA,UACD;AAAA,QAAA,OACM;AACN,gBAAM,KAAK,OAAO,6BAA6B,KAAK,EAAA,OAAEA,SAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,YAAA,GAAe,iBAAiB;AAAA,QAAA;AAAA,MAChJ;AAED,aAAO,IAAIa,SAAAA,WAAW,IAAI,aAAa,QAAQ,KAAK,CAAC;AAAA,IAAA,CACrD;AAAA,EAAA;AAAA,EAGF,MAAc,oBAAoB,aAAsC;AACvE,UAAM,SAAS,MAAM,KAAK,sBAAsB,UAAU,WAAW;AAE9D,WAAA,QAAQ,SAAS,QAAQ,2BAA2BC,YAAA;AAAA,EAAA;AAE7D;AAIO,MAAM,aAAa;AAAA,EACzB,YAA4B,QAAkD,OAAgB;AAAlE,SAAA,SAAA;AAAkD,SAAA,QAAA;AAAA,EAAA;AAC/E;;;"}
|
|
@@ -73,11 +73,11 @@ class InviteManager {
|
|
|
73
73
|
if (isNew) {
|
|
74
74
|
await this.mailer.sendNewUserInvitedMail(
|
|
75
75
|
trx,
|
|
76
|
-
{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken },
|
|
76
|
+
{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken, memberships },
|
|
77
77
|
customMailOptions
|
|
78
78
|
);
|
|
79
79
|
} else {
|
|
80
|
-
await this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug }, customMailOptions);
|
|
80
|
+
await this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug, memberships }, customMailOptions);
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
return new ResponseOk(new InviteResult(person, isNew));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InviteManager.js","sources":["../../../../../../packages/engine-tenant-api/src/model/service/InviteManager.ts"],"sourcesContent":["import {\n\tCreateIdentityCommand,\n\tCreateOrUpdateProjectMembershipCommand,\n\tCreatePersonCommand,\n\tCreatePersonTokenCommand,\n\tSavePersonTokenCommand,\n} from '../commands'\nimport { Providers } from '../providers'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { Project, ProjectSchemaResolver } from '../type'\nimport { InviteErrorCode, InviteMethod } from '../../schema'\nimport { TenantRole } from '../authorization'\nimport { UserMailer } from '../mailing'\nimport { createAppendMembershipVariables } from './membershipUtils'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DatabaseContext, TokenHash } from '../utils'\nimport { NoPassword, PasswordPlain } from '../dtos'\nimport { Acl } from '@contember/schema'\nimport { validateEmail } from '../utils/email'\nimport { INVITATION_RESET_TOKEN_EXPIRATION_MINUTES } from '../consts/expirations'\n\nexport interface InviteData {\n\temail: string\n\tname?: string\n\tproject: Project\n\tmemberships: readonly Acl.Membership[]\n\tnoEmail?: boolean\n\tpassword?: string\n\temailVariant?: string\n\tmethod?: InviteMethod\n\tpasswordResetTokenHash?: TokenHash\n}\n\nexport class InviteManager {\n\tconstructor(\n\t\tprivate readonly providers: Providers,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectSchemaResolver: ProjectSchemaResolver,\n\t) { }\n\n\tasync invite(\n\t\tdbContext: DatabaseContext,\n\t\t{\n\t\t\temail,\n\t\t\tproject,\n\t\t\tname,\n\t\t\tmemberships,\n\t\t\tmethod,\n\t\t\temailVariant,\n\t\t\tnoEmail = false,\n\t\t\tpassword,\n\t\t\tpasswordResetTokenHash,\n\t\t}: InviteData,\n\t): Promise<InviteResponse> {\n\t\tif (!validateEmail(email.trim())) {\n\t\t\treturn new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')\n\t\t}\n\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tlet person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tconst isNew = !person\n\t\t\tlet generatedPassword: string | null = null\n\t\t\tlet resetToken: string | null = null\n\t\t\tif (!person) {\n\t\t\t\tconst identityId = await trx.commandBus.execute(new CreateIdentityCommand([TenantRole.PERSON]))\n\n\t\t\t\tgeneratedPassword = password ??\n\t\t\t\t\t(method === 'CREATE_PASSWORD' ? (await this.providers.randomBytes(9)).toString('base64') : null)\n\t\t\t\tconst passwordWrapper = generatedPassword !== null ? new PasswordPlain(generatedPassword) : NoPassword\n\n\t\t\t\tperson = await trx.commandBus.execute(new CreatePersonCommand({\n\t\t\t\t\tidentityId,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\tpassword: passwordWrapper,\n\t\t\t\t}))\n\t\t\t\tif (method === 'RESET_PASSWORD') {\n\t\t\t\t\tconst result = await trx.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id, await this.getInviteExpiration(project.slug)))\n\t\t\t\t\tresetToken = result.token\n\t\t\t\t}\n\t\t\t\tif (passwordResetTokenHash) {\n\t\t\t\t\tawait trx.commandBus.execute(new SavePersonTokenCommand(person.id, passwordResetTokenHash, 'password_reset', await this.getInviteExpiration(project.slug)))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const membershipUpdate of createAppendMembershipVariables(memberships)) {\n\t\t\t\tawait trx.commandBus.execute(new CreateOrUpdateProjectMembershipCommand(project.id, person.identity_id, membershipUpdate))\n\t\t\t}\n\t\t\tif (!noEmail) {\n\t\t\t\tconst customMailOptions = {\n\t\t\t\t\tprojectId: project.id,\n\t\t\t\t\tvariant: emailVariant ?? '',\n\t\t\t\t}\n\t\t\t\tif (isNew) {\n\t\t\t\t\tawait this.mailer.sendNewUserInvitedMail(\n\t\t\t\t\t\ttrx,\n\t\t\t\t\t\t{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken },\n\t\t\t\t\t\tcustomMailOptions,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug }, customMailOptions)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn new ResponseOk(new InviteResult(person, isNew))\n\t\t})\n\t}\n\n\tprivate async getInviteExpiration(projectSlug: string): Promise<number> {\n\t\tconst schema = await this.projectSchemaResolver.getSchema(projectSlug)\n\n\t\treturn schema?.settings.tenant?.inviteExpirationMinutes ?? INVITATION_RESET_TOKEN_EXPIRATION_MINUTES\n\t}\n}\n\nexport type InviteResponse = Response<InviteResult, InviteErrorCode>\n\nexport class InviteResult {\n\tconstructor(public readonly person: Omit<PersonRow, 'roles'>, public readonly isNew: boolean) { }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiCO,MAAM,cAAc;AAAA,EAC1B,YACkB,WACA,QACA,uBAChB;AAHgB,SAAA,YAAA;AACA,SAAA,SAAA;AACA,SAAA,wBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,OACL,WACA;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,GAEyB;AAC1B,QAAI,CAAC,cAAc,MAAM,KAAM,CAAA,GAAG;AAC1B,aAAA,IAAI,cAAc,wBAAwB,yCAAyC;AAAA,IAAA;AAG3F,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC3C,UAAA,SAA0C,MAAM,IAAI,aAAa,MAAM,YAAY,QAAQ,KAAK,CAAC;AACrG,YAAM,QAAQ,CAAC;AACf,UAAI,oBAAmC;AACvC,UAAI,aAA4B;AAChC,UAAI,CAAC,QAAQ;AACN,cAAA,aAAa,MAAM,IAAI,WAAW,QAAQ,IAAI,sBAAsB,CAAC,WAAW,MAAM,CAAC,CAAC;AAE1E,4BAAA,aAClB,WAAW,qBAAqB,MAAM,KAAK,UAAU,YAAY,CAAC,GAAG,SAAS,QAAQ,IAAI;AAC5F,cAAM,kBAAkB,sBAAsB,OAAO,IAAI,cAAc,iBAAiB,IAAI;AAE5F,iBAAS,MAAM,IAAI,WAAW,QAAQ,IAAI,oBAAoB;AAAA,UAC7D;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU;AAAA,QAAA,CACV,CAAC;AACF,YAAI,WAAW,kBAAkB;AAChC,gBAAM,SAAS,MAAM,IAAI,WAAW,QAAQ,yBAAyB,2BAA2B,OAAO,IAAI,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AACxJ,uBAAa,OAAO;AAAA,QAAA;AAErB,YAAI,wBAAwB;AAC3B,gBAAM,IAAI,WAAW,QAAQ,IAAI,uBAAuB,OAAO,IAAI,wBAAwB,kBAAkB,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AAAA,QAAA;AAAA,MAC3J;AAEU,iBAAA,oBAAoB,gCAAgC,WAAW,GAAG;AACtE,cAAA,IAAI,WAAW,QAAQ,IAAI,uCAAuC,QAAQ,IAAI,OAAO,aAAa,gBAAgB,CAAC;AAAA,MAAA;AAE1H,UAAI,CAAC,SAAS;AACb,cAAM,oBAAoB;AAAA,UACzB,WAAW,QAAQ;AAAA,UACnB,SAAS,gBAAgB;AAAA,QAC1B;AACA,YAAI,OAAO;AACV,gBAAM,KAAK,OAAO;AAAA,YACjB;AAAA,YACA,EAAE,OAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU,mBAAmB,OAAO,
|
|
1
|
+
{"version":3,"file":"InviteManager.js","sources":["../../../../../../packages/engine-tenant-api/src/model/service/InviteManager.ts"],"sourcesContent":["import {\n\tCreateIdentityCommand,\n\tCreateOrUpdateProjectMembershipCommand,\n\tCreatePersonCommand,\n\tCreatePersonTokenCommand,\n\tSavePersonTokenCommand,\n} from '../commands'\nimport { Providers } from '../providers'\nimport { PersonQuery, PersonRow } from '../queries'\nimport { Project, ProjectSchemaResolver } from '../type'\nimport { InviteErrorCode, InviteMethod } from '../../schema'\nimport { TenantRole } from '../authorization'\nimport { UserMailer } from '../mailing'\nimport { createAppendMembershipVariables } from './membershipUtils'\nimport { Response, ResponseError, ResponseOk } from '../utils/Response'\nimport { DatabaseContext, TokenHash } from '../utils'\nimport { NoPassword, PasswordPlain } from '../dtos'\nimport { Acl } from '@contember/schema'\nimport { validateEmail } from '../utils/email'\nimport { INVITATION_RESET_TOKEN_EXPIRATION_MINUTES } from '../consts/expirations'\n\nexport interface InviteData {\n\temail: string\n\tname?: string\n\tproject: Project\n\tmemberships: readonly Acl.Membership[]\n\tnoEmail?: boolean\n\tpassword?: string\n\temailVariant?: string\n\tmethod?: InviteMethod\n\tpasswordResetTokenHash?: TokenHash\n}\n\nexport class InviteManager {\n\tconstructor(\n\t\tprivate readonly providers: Providers,\n\t\tprivate readonly mailer: UserMailer,\n\t\tprivate readonly projectSchemaResolver: ProjectSchemaResolver,\n\t) { }\n\n\tasync invite(\n\t\tdbContext: DatabaseContext,\n\t\t{\n\t\t\temail,\n\t\t\tproject,\n\t\t\tname,\n\t\t\tmemberships,\n\t\t\tmethod,\n\t\t\temailVariant,\n\t\t\tnoEmail = false,\n\t\t\tpassword,\n\t\t\tpasswordResetTokenHash,\n\t\t}: InviteData,\n\t): Promise<InviteResponse> {\n\t\tif (!validateEmail(email.trim())) {\n\t\t\treturn new ResponseError('INVALID_EMAIL_FORMAT', 'E-mail address is not in a valid format')\n\t\t}\n\n\t\treturn await dbContext.transaction(async trx => {\n\t\t\tlet person: Omit<PersonRow, 'roles'> | null = await trx.queryHandler.fetch(PersonQuery.byEmail(email))\n\t\t\tconst isNew = !person\n\t\t\tlet generatedPassword: string | null = null\n\t\t\tlet resetToken: string | null = null\n\t\t\tif (!person) {\n\t\t\t\tconst identityId = await trx.commandBus.execute(new CreateIdentityCommand([TenantRole.PERSON]))\n\n\t\t\t\tgeneratedPassword = password ??\n\t\t\t\t\t(method === 'CREATE_PASSWORD' ? (await this.providers.randomBytes(9)).toString('base64') : null)\n\t\t\t\tconst passwordWrapper = generatedPassword !== null ? new PasswordPlain(generatedPassword) : NoPassword\n\n\t\t\t\tperson = await trx.commandBus.execute(new CreatePersonCommand({\n\t\t\t\t\tidentityId,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\tpassword: passwordWrapper,\n\t\t\t\t}))\n\t\t\t\tif (method === 'RESET_PASSWORD') {\n\t\t\t\t\tconst result = await trx.commandBus.execute(CreatePersonTokenCommand.createPasswordResetRequest(person.id, await this.getInviteExpiration(project.slug)))\n\t\t\t\t\tresetToken = result.token\n\t\t\t\t}\n\t\t\t\tif (passwordResetTokenHash) {\n\t\t\t\t\tawait trx.commandBus.execute(new SavePersonTokenCommand(person.id, passwordResetTokenHash, 'password_reset', await this.getInviteExpiration(project.slug)))\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (const membershipUpdate of createAppendMembershipVariables(memberships)) {\n\t\t\t\tawait trx.commandBus.execute(new CreateOrUpdateProjectMembershipCommand(project.id, person.identity_id, membershipUpdate))\n\t\t\t}\n\t\t\tif (!noEmail) {\n\t\t\t\tconst customMailOptions = {\n\t\t\t\t\tprojectId: project.id,\n\t\t\t\t\tvariant: emailVariant ?? '',\n\t\t\t\t}\n\t\t\t\tif (isNew) {\n\t\t\t\t\tawait this.mailer.sendNewUserInvitedMail(\n\t\t\t\t\t\ttrx,\n\t\t\t\t\t\t{ email, project: project.name, projectSlug: project.slug, password: generatedPassword, token: resetToken, memberships },\n\t\t\t\t\t\tcustomMailOptions,\n\t\t\t\t\t)\n\t\t\t\t} else {\n\t\t\t\t\tawait this.mailer.sendExistingUserInvitedEmail(trx, { email, project: project.name, projectSlug: project.slug, memberships }, customMailOptions)\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn new ResponseOk(new InviteResult(person, isNew))\n\t\t})\n\t}\n\n\tprivate async getInviteExpiration(projectSlug: string): Promise<number> {\n\t\tconst schema = await this.projectSchemaResolver.getSchema(projectSlug)\n\n\t\treturn schema?.settings.tenant?.inviteExpirationMinutes ?? INVITATION_RESET_TOKEN_EXPIRATION_MINUTES\n\t}\n}\n\nexport type InviteResponse = Response<InviteResult, InviteErrorCode>\n\nexport class InviteResult {\n\tconstructor(public readonly person: Omit<PersonRow, 'roles'>, public readonly isNew: boolean) { }\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AAiCO,MAAM,cAAc;AAAA,EAC1B,YACkB,WACA,QACA,uBAChB;AAHgB,SAAA,YAAA;AACA,SAAA,SAAA;AACA,SAAA,wBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,OACL,WACA;AAAA,IACC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU;AAAA,IACV;AAAA,IACA;AAAA,EAAA,GAEyB;AAC1B,QAAI,CAAC,cAAc,MAAM,KAAM,CAAA,GAAG;AAC1B,aAAA,IAAI,cAAc,wBAAwB,yCAAyC;AAAA,IAAA;AAG3F,WAAO,MAAM,UAAU,YAAY,OAAM,QAAO;AAC3C,UAAA,SAA0C,MAAM,IAAI,aAAa,MAAM,YAAY,QAAQ,KAAK,CAAC;AACrG,YAAM,QAAQ,CAAC;AACf,UAAI,oBAAmC;AACvC,UAAI,aAA4B;AAChC,UAAI,CAAC,QAAQ;AACN,cAAA,aAAa,MAAM,IAAI,WAAW,QAAQ,IAAI,sBAAsB,CAAC,WAAW,MAAM,CAAC,CAAC;AAE1E,4BAAA,aAClB,WAAW,qBAAqB,MAAM,KAAK,UAAU,YAAY,CAAC,GAAG,SAAS,QAAQ,IAAI;AAC5F,cAAM,kBAAkB,sBAAsB,OAAO,IAAI,cAAc,iBAAiB,IAAI;AAE5F,iBAAS,MAAM,IAAI,WAAW,QAAQ,IAAI,oBAAoB;AAAA,UAC7D;AAAA,UACA;AAAA,UACA;AAAA,UACA,UAAU;AAAA,QAAA,CACV,CAAC;AACF,YAAI,WAAW,kBAAkB;AAChC,gBAAM,SAAS,MAAM,IAAI,WAAW,QAAQ,yBAAyB,2BAA2B,OAAO,IAAI,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AACxJ,uBAAa,OAAO;AAAA,QAAA;AAErB,YAAI,wBAAwB;AAC3B,gBAAM,IAAI,WAAW,QAAQ,IAAI,uBAAuB,OAAO,IAAI,wBAAwB,kBAAkB,MAAM,KAAK,oBAAoB,QAAQ,IAAI,CAAC,CAAC;AAAA,QAAA;AAAA,MAC3J;AAEU,iBAAA,oBAAoB,gCAAgC,WAAW,GAAG;AACtE,cAAA,IAAI,WAAW,QAAQ,IAAI,uCAAuC,QAAQ,IAAI,OAAO,aAAa,gBAAgB,CAAC;AAAA,MAAA;AAE1H,UAAI,CAAC,SAAS;AACb,cAAM,oBAAoB;AAAA,UACzB,WAAW,QAAQ;AAAA,UACnB,SAAS,gBAAgB;AAAA,QAC1B;AACA,YAAI,OAAO;AACV,gBAAM,KAAK,OAAO;AAAA,YACjB;AAAA,YACA,EAAE,OAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,UAAU,mBAAmB,OAAO,YAAY,YAAY;AAAA,YACvH;AAAA,UACD;AAAA,QAAA,OACM;AACN,gBAAM,KAAK,OAAO,6BAA6B,KAAK,EAAE,OAAO,SAAS,QAAQ,MAAM,aAAa,QAAQ,MAAM,YAAA,GAAe,iBAAiB;AAAA,QAAA;AAAA,MAChJ;AAED,aAAO,IAAI,WAAW,IAAI,aAAa,QAAQ,KAAK,CAAC;AAAA,IAAA,CACrD;AAAA,EAAA;AAAA,EAGF,MAAc,oBAAoB,aAAsC;AACvE,UAAM,SAAS,MAAM,KAAK,sBAAsB,UAAU,WAAW;AAE9D,WAAA,QAAQ,SAAS,QAAQ,2BAA2B;AAAA,EAAA;AAE7D;AAIO,MAAM,aAAa;AAAA,EACzB,YAA4B,QAAkD,OAAgB;AAAlE,SAAA,SAAA;AAAkD,SAAA,QAAA;AAAA,EAAA;AAC/E;"}
|
|
@@ -7,7 +7,7 @@ class NodeMailer {
|
|
|
7
7
|
this.from = from;
|
|
8
8
|
this.transport = transport;
|
|
9
9
|
}
|
|
10
|
-
async send(message) {
|
|
10
|
+
async send({ variables, template, ...message }) {
|
|
11
11
|
try {
|
|
12
12
|
return await this.transport.sendMail({ ...message, from: message.from || this.from });
|
|
13
13
|
} catch (e) {
|
|
@@ -16,10 +16,38 @@ class NodeMailer {
|
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
|
+
class WebhookMailer {
|
|
20
|
+
constructor(from, url, headers = {}) {
|
|
21
|
+
this.from = from;
|
|
22
|
+
this.url = url;
|
|
23
|
+
this.headers = headers;
|
|
24
|
+
}
|
|
25
|
+
async send(message) {
|
|
26
|
+
const response = await fetch(this.url, {
|
|
27
|
+
method: "POST",
|
|
28
|
+
body: JSON.stringify({ ...message, from: message.from || this.from }),
|
|
29
|
+
headers: {
|
|
30
|
+
...this.headers,
|
|
31
|
+
"Content-Type": "application/json"
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
if (!response.ok) {
|
|
35
|
+
logger.logger.error("Mail sending failed", { mail: message, response });
|
|
36
|
+
return { error: response.statusText };
|
|
37
|
+
}
|
|
38
|
+
await response.text();
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
}
|
|
19
42
|
const createMailer = (options) => {
|
|
20
43
|
const { from, ...transportOptions } = options;
|
|
44
|
+
if ("webhook" in transportOptions) {
|
|
45
|
+
const config = typeof transportOptions.webhook === "string" ? { url: transportOptions.webhook } : transportOptions.webhook;
|
|
46
|
+
return new WebhookMailer(from, config.url, config.headers);
|
|
47
|
+
}
|
|
21
48
|
return new NodeMailer(from, nodemailer.createTransport(transportOptions));
|
|
22
49
|
};
|
|
23
50
|
exports.NodeMailer = NodeMailer;
|
|
51
|
+
exports.WebhookMailer = WebhookMailer;
|
|
24
52
|
exports.createMailer = createMailer;
|
|
25
53
|
//# sourceMappingURL=Mailer.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Mailer.cjs","sources":["../../../../../packages/engine-tenant-api/src/utils/Mailer.ts"],"sourcesContent":["import { createTransport, Transporter, TransportOptions } from 'nodemailer'\nimport
|
|
1
|
+
{"version":3,"file":"Mailer.cjs","sources":["../../../../../packages/engine-tenant-api/src/utils/Mailer.ts"],"sourcesContent":["import { createTransport, Transporter, TransportOptions } from 'nodemailer'\nimport * as SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport * as SMTPPool from 'nodemailer/lib/smtp-pool'\nimport * as SendmailTransport from 'nodemailer/lib/sendmail-transport'\nimport * as SESTransport from 'nodemailer/lib/ses-transport'\nimport { logger } from '@contember/logger'\nimport { MailTemplateIdentifier } from '../model'\n\nexport interface MailMessage {\n\tto: string\n\tsubject: string\n\thtml: string\n\tfrom?: string\n\treplyTo?: string\n\tvariables?: Record<string, any>\n\ttemplate?: MailTemplateIdentifier\n}\n\nexport interface SentInfo {\n\terror?: any\n\tmessageId?: string\n\tenvelope?: any\n\taccepted?: string[]\n\trejected?: string[]\n\tpending?: string[]\n}\n\nexport type MailerOptions =\n\t& (\n\t\tSMTPTransport.Options | SMTPPool.Options | SendmailTransport.Options | SESTransport.Options | TransportOptions\n\t\t| {\n\t\t\twebhook: string | {\n\t\t\t\turl: string\n\t\t\t\theaders?: Record<string, string>\n\t\t\t}\n\t\t}\n\t)\n\t& {\n\t\tfrom?: string\n\t}\n\n\nexport interface Mailer {\n\tsend(message: MailMessage): Promise<SentInfo>\n}\n\nexport class NodeMailer implements Mailer {\n\tconstructor(private readonly from: string | undefined, private readonly transport: Transporter) {}\n\n\tpublic async send({ variables, template, ...message }: MailMessage): Promise<SentInfo> {\n\t\ttry {\n\t\t\treturn await this.transport.sendMail({ ...message, from: message.from || this.from })\n\t\t} catch (e) {\n\t\t\tlogger.error(e, { message: 'Mail sending failed', mail: message })\n\t\t\treturn { error: e }\n\t\t}\n\t}\n}\n\nexport class WebhookMailer implements Mailer {\n\n\tconstructor(\n\t\tprivate readonly from: string | undefined,\n\t\tprivate readonly url: string,\n\t\tprivate readonly headers: Record<string, string> = {},\n\t) {\n\t}\n\n\tpublic async send(message: MailMessage): Promise<SentInfo> {\n\t\tconst response = await fetch(this.url, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: JSON.stringify({ ...message, from: message.from || this.from }),\n\t\t\theaders: {\n\t\t\t\t...this.headers,\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t},\n\t\t})\n\t\tif (!response.ok) {\n\t\t\tlogger.error('Mail sending failed', { mail: message, response })\n\t\t\treturn { error: response.statusText }\n\t\t}\n\t\tawait response.text()\n\t\treturn {}\n\t}\n}\n\n\nexport const createMailer = (options: MailerOptions): Mailer => {\n\tconst { from, ...transportOptions } = options\n\tif ('webhook' in transportOptions) {\n\t\tconst config = typeof transportOptions.webhook === 'string' ? { url: transportOptions.webhook } : transportOptions.webhook\n\t\treturn new WebhookMailer(from, config.url, config.headers)\n\t}\n\n\treturn new NodeMailer(from, createTransport(transportOptions as any))\n}\n"],"names":["logger","createTransport"],"mappings":";;;;AA8CO,MAAM,WAA6B;AAAA,EACzC,YAA6B,MAA2C,WAAwB;AAAnE,SAAA,OAAA;AAA2C,SAAA,YAAA;AAAA,EAAA;AAAA,EAExE,MAAa,KAAK,EAAE,WAAW,UAAU,GAAG,WAA2C;AAClF,QAAA;AACH,aAAO,MAAM,KAAK,UAAU,SAAS,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,KAAA,CAAM;AAAA,aAC5E,GAAG;AACXA,aAAA,OAAO,MAAM,GAAG,EAAE,SAAS,uBAAuB,MAAM,SAAS;AAC1D,aAAA,EAAE,OAAO,EAAE;AAAA,IAAA;AAAA,EACnB;AAEF;AAEO,MAAM,cAAgC;AAAA,EAE5C,YACkB,MACA,KACA,UAAkC,CAAA,GAClD;AAHgB,SAAA,OAAA;AACA,SAAA,MAAA;AACA,SAAA,UAAA;AAAA,EAAA;AAAA,EAIlB,MAAa,KAAK,SAAyC;AAC1D,UAAM,WAAW,MAAM,MAAM,KAAK,KAAK;AAAA,MACtC,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,MAAM;AAAA,MACpE,SAAS;AAAA,QACR,GAAG,KAAK;AAAA,QACR,gBAAgB;AAAA,MAAA;AAAA,IACjB,CACA;AACG,QAAA,CAAC,SAAS,IAAI;AACjBA,aAAA,OAAO,MAAM,uBAAuB,EAAE,MAAM,SAAS,UAAU;AACxD,aAAA,EAAE,OAAO,SAAS,WAAW;AAAA,IAAA;AAErC,UAAM,SAAS,KAAK;AACpB,WAAO,CAAC;AAAA,EAAA;AAEV;AAGa,MAAA,eAAe,CAAC,YAAmC;AAC/D,QAAM,EAAE,MAAM,GAAG,iBAAA,IAAqB;AACtC,MAAI,aAAa,kBAAkB;AAC5B,UAAA,SAAS,OAAO,iBAAiB,YAAY,WAAW,EAAE,KAAK,iBAAiB,YAAY,iBAAiB;AACnH,WAAO,IAAI,cAAc,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,EAAA;AAG1D,SAAO,IAAI,WAAW,MAAMC,WAAA,gBAAgB,gBAAuB,CAAC;AACrE;;;;"}
|
|
@@ -5,7 +5,7 @@ class NodeMailer {
|
|
|
5
5
|
this.from = from;
|
|
6
6
|
this.transport = transport;
|
|
7
7
|
}
|
|
8
|
-
async send(message) {
|
|
8
|
+
async send({ variables, template, ...message }) {
|
|
9
9
|
try {
|
|
10
10
|
return await this.transport.sendMail({ ...message, from: message.from || this.from });
|
|
11
11
|
} catch (e) {
|
|
@@ -14,12 +14,40 @@ class NodeMailer {
|
|
|
14
14
|
}
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
class WebhookMailer {
|
|
18
|
+
constructor(from, url, headers = {}) {
|
|
19
|
+
this.from = from;
|
|
20
|
+
this.url = url;
|
|
21
|
+
this.headers = headers;
|
|
22
|
+
}
|
|
23
|
+
async send(message) {
|
|
24
|
+
const response = await fetch(this.url, {
|
|
25
|
+
method: "POST",
|
|
26
|
+
body: JSON.stringify({ ...message, from: message.from || this.from }),
|
|
27
|
+
headers: {
|
|
28
|
+
...this.headers,
|
|
29
|
+
"Content-Type": "application/json"
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
if (!response.ok) {
|
|
33
|
+
logger.error("Mail sending failed", { mail: message, response });
|
|
34
|
+
return { error: response.statusText };
|
|
35
|
+
}
|
|
36
|
+
await response.text();
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
17
40
|
const createMailer = (options) => {
|
|
18
41
|
const { from, ...transportOptions } = options;
|
|
42
|
+
if ("webhook" in transportOptions) {
|
|
43
|
+
const config = typeof transportOptions.webhook === "string" ? { url: transportOptions.webhook } : transportOptions.webhook;
|
|
44
|
+
return new WebhookMailer(from, config.url, config.headers);
|
|
45
|
+
}
|
|
19
46
|
return new NodeMailer(from, createTransport(transportOptions));
|
|
20
47
|
};
|
|
21
48
|
export {
|
|
22
49
|
NodeMailer,
|
|
50
|
+
WebhookMailer,
|
|
23
51
|
createMailer
|
|
24
52
|
};
|
|
25
53
|
//# sourceMappingURL=Mailer.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Mailer.js","sources":["../../../../../packages/engine-tenant-api/src/utils/Mailer.ts"],"sourcesContent":["import { createTransport, Transporter, TransportOptions } from 'nodemailer'\nimport
|
|
1
|
+
{"version":3,"file":"Mailer.js","sources":["../../../../../packages/engine-tenant-api/src/utils/Mailer.ts"],"sourcesContent":["import { createTransport, Transporter, TransportOptions } from 'nodemailer'\nimport * as SMTPTransport from 'nodemailer/lib/smtp-transport'\nimport * as SMTPPool from 'nodemailer/lib/smtp-pool'\nimport * as SendmailTransport from 'nodemailer/lib/sendmail-transport'\nimport * as SESTransport from 'nodemailer/lib/ses-transport'\nimport { logger } from '@contember/logger'\nimport { MailTemplateIdentifier } from '../model'\n\nexport interface MailMessage {\n\tto: string\n\tsubject: string\n\thtml: string\n\tfrom?: string\n\treplyTo?: string\n\tvariables?: Record<string, any>\n\ttemplate?: MailTemplateIdentifier\n}\n\nexport interface SentInfo {\n\terror?: any\n\tmessageId?: string\n\tenvelope?: any\n\taccepted?: string[]\n\trejected?: string[]\n\tpending?: string[]\n}\n\nexport type MailerOptions =\n\t& (\n\t\tSMTPTransport.Options | SMTPPool.Options | SendmailTransport.Options | SESTransport.Options | TransportOptions\n\t\t| {\n\t\t\twebhook: string | {\n\t\t\t\turl: string\n\t\t\t\theaders?: Record<string, string>\n\t\t\t}\n\t\t}\n\t)\n\t& {\n\t\tfrom?: string\n\t}\n\n\nexport interface Mailer {\n\tsend(message: MailMessage): Promise<SentInfo>\n}\n\nexport class NodeMailer implements Mailer {\n\tconstructor(private readonly from: string | undefined, private readonly transport: Transporter) {}\n\n\tpublic async send({ variables, template, ...message }: MailMessage): Promise<SentInfo> {\n\t\ttry {\n\t\t\treturn await this.transport.sendMail({ ...message, from: message.from || this.from })\n\t\t} catch (e) {\n\t\t\tlogger.error(e, { message: 'Mail sending failed', mail: message })\n\t\t\treturn { error: e }\n\t\t}\n\t}\n}\n\nexport class WebhookMailer implements Mailer {\n\n\tconstructor(\n\t\tprivate readonly from: string | undefined,\n\t\tprivate readonly url: string,\n\t\tprivate readonly headers: Record<string, string> = {},\n\t) {\n\t}\n\n\tpublic async send(message: MailMessage): Promise<SentInfo> {\n\t\tconst response = await fetch(this.url, {\n\t\t\tmethod: 'POST',\n\t\t\tbody: JSON.stringify({ ...message, from: message.from || this.from }),\n\t\t\theaders: {\n\t\t\t\t...this.headers,\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t},\n\t\t})\n\t\tif (!response.ok) {\n\t\t\tlogger.error('Mail sending failed', { mail: message, response })\n\t\t\treturn { error: response.statusText }\n\t\t}\n\t\tawait response.text()\n\t\treturn {}\n\t}\n}\n\n\nexport const createMailer = (options: MailerOptions): Mailer => {\n\tconst { from, ...transportOptions } = options\n\tif ('webhook' in transportOptions) {\n\t\tconst config = typeof transportOptions.webhook === 'string' ? { url: transportOptions.webhook } : transportOptions.webhook\n\t\treturn new WebhookMailer(from, config.url, config.headers)\n\t}\n\n\treturn new NodeMailer(from, createTransport(transportOptions as any))\n}\n"],"names":[],"mappings":";;AA8CO,MAAM,WAA6B;AAAA,EACzC,YAA6B,MAA2C,WAAwB;AAAnE,SAAA,OAAA;AAA2C,SAAA,YAAA;AAAA,EAAA;AAAA,EAExE,MAAa,KAAK,EAAE,WAAW,UAAU,GAAG,WAA2C;AAClF,QAAA;AACH,aAAO,MAAM,KAAK,UAAU,SAAS,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,KAAA,CAAM;AAAA,aAC5E,GAAG;AACX,aAAO,MAAM,GAAG,EAAE,SAAS,uBAAuB,MAAM,SAAS;AAC1D,aAAA,EAAE,OAAO,EAAE;AAAA,IAAA;AAAA,EACnB;AAEF;AAEO,MAAM,cAAgC;AAAA,EAE5C,YACkB,MACA,KACA,UAAkC,CAAA,GAClD;AAHgB,SAAA,OAAA;AACA,SAAA,MAAA;AACA,SAAA,UAAA;AAAA,EAAA;AAAA,EAIlB,MAAa,KAAK,SAAyC;AAC1D,UAAM,WAAW,MAAM,MAAM,KAAK,KAAK;AAAA,MACtC,QAAQ;AAAA,MACR,MAAM,KAAK,UAAU,EAAE,GAAG,SAAS,MAAM,QAAQ,QAAQ,KAAK,MAAM;AAAA,MACpE,SAAS;AAAA,QACR,GAAG,KAAK;AAAA,QACR,gBAAgB;AAAA,MAAA;AAAA,IACjB,CACA;AACG,QAAA,CAAC,SAAS,IAAI;AACjB,aAAO,MAAM,uBAAuB,EAAE,MAAM,SAAS,UAAU;AACxD,aAAA,EAAE,OAAO,SAAS,WAAW;AAAA,IAAA;AAErC,UAAM,SAAS,KAAK;AACpB,WAAO,CAAC;AAAA,EAAA;AAEV;AAGa,MAAA,eAAe,CAAC,YAAmC;AAC/D,QAAM,EAAE,MAAM,GAAG,iBAAA,IAAqB;AACtC,MAAI,aAAa,kBAAkB;AAC5B,UAAA,SAAS,OAAO,iBAAiB,YAAY,WAAW,EAAE,KAAK,iBAAiB,YAAY,iBAAiB;AACnH,WAAO,IAAI,cAAc,MAAM,OAAO,KAAK,OAAO,OAAO;AAAA,EAAA;AAG1D,SAAO,IAAI,WAAW,MAAM,gBAAgB,gBAAuB,CAAC;AACrE;"}
|
|
@@ -289,6 +289,7 @@ exports.ResolverFactory = ResolverFactory.ResolverFactory;
|
|
|
289
289
|
exports.TenantResolverContextFactory = TenantResolverContextFactory.TenantResolverContextFactory;
|
|
290
290
|
exports.createResolverContext = TenantResolverContextFactory.createResolverContext;
|
|
291
291
|
exports.NodeMailer = Mailer.NodeMailer;
|
|
292
|
+
exports.WebhookMailer = Mailer.WebhookMailer;
|
|
292
293
|
exports.createMailer = Mailer.createMailer;
|
|
293
294
|
exports.TemplateRenderer = TemplateRenderer.TemplateRenderer;
|
|
294
295
|
exports.TenantMigrationsRunner = runner.TenantMigrationsRunner;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/production/index.js
CHANGED
|
@@ -126,7 +126,7 @@ import { IdentityTypeResolver } from "./src/resolvers/types/IdentityTypeResolver
|
|
|
126
126
|
import { ProjectTypeResolver } from "./src/resolvers/types/ProjectTypeResolver.js";
|
|
127
127
|
import { ResolverFactory } from "./src/resolvers/ResolverFactory.js";
|
|
128
128
|
import { TenantResolverContextFactory, createResolverContext } from "./src/resolvers/TenantResolverContextFactory.js";
|
|
129
|
-
import { NodeMailer, createMailer } from "./src/utils/Mailer.js";
|
|
129
|
+
import { NodeMailer, WebhookMailer, createMailer } from "./src/utils/Mailer.js";
|
|
130
130
|
import { TemplateRenderer } from "./src/utils/TemplateRenderer.js";
|
|
131
131
|
import { TenantMigrationsRunner } from "./src/migrations/runner.js";
|
|
132
132
|
import { default as default2 } from "./src/schema/tenant.graphql.js";
|
|
@@ -279,6 +279,7 @@ export {
|
|
|
279
279
|
UserMailer,
|
|
280
280
|
VerifyErrorCode,
|
|
281
281
|
VerifyResult,
|
|
282
|
+
WebhookMailer,
|
|
282
283
|
computeTokenHash,
|
|
283
284
|
createMailer,
|
|
284
285
|
createResolverContext,
|
|
@@ -20,43 +20,49 @@ class UserMailer {
|
|
|
20
20
|
this.templateRenderer = templateRenderer;
|
|
21
21
|
}
|
|
22
22
|
async sendNewUserInvitedMail(dbContext, mailArguments, customMailOptions) {
|
|
23
|
-
const
|
|
23
|
+
const templateId = { type: type.MailType.newUserInvited, ...customMailOptions };
|
|
24
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
24
25
|
subject: "You have been invited to {{project}}",
|
|
25
26
|
content: NewUserInvited_mustache,
|
|
26
27
|
replyTo: null
|
|
27
28
|
};
|
|
28
|
-
await this.
|
|
29
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
29
30
|
}
|
|
30
31
|
async sendExistingUserInvitedEmail(dbContext, mailArguments, customMailOptions) {
|
|
31
|
-
const
|
|
32
|
+
const templateId = { type: type.MailType.existingUserInvited, ...customMailOptions };
|
|
33
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
32
34
|
subject: "You have been invited to {{project}}",
|
|
33
35
|
content: ExistingUserInvited_mustache,
|
|
34
36
|
replyTo: null
|
|
35
37
|
};
|
|
36
|
-
await this.
|
|
38
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
37
39
|
}
|
|
38
40
|
async sendPasswordResetEmail(dbContext, mailArguments, customMailOptions) {
|
|
39
|
-
const
|
|
41
|
+
const templateId = { type: type.MailType.passwordReset, ...customMailOptions };
|
|
42
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
40
43
|
subject: "Password reset",
|
|
41
44
|
content: PasswordReset_mustache,
|
|
42
45
|
replyTo: null
|
|
43
46
|
};
|
|
44
|
-
await this.
|
|
47
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
45
48
|
}
|
|
46
49
|
async sendPasswordlessEmail(dbContext, mailArguments, customMailOptions) {
|
|
47
|
-
const
|
|
50
|
+
const templateId = { type: type.MailType.passwordlessSignIn, ...customMailOptions };
|
|
51
|
+
const template = await this.getCustomTemplate(dbContext, templateId) || {
|
|
48
52
|
subject: "Sign in here",
|
|
49
53
|
content: PasswordlessSignIn_mustache,
|
|
50
54
|
replyTo: null
|
|
51
55
|
};
|
|
52
|
-
await this.
|
|
56
|
+
await this.sendMail(templateId, template, mailArguments);
|
|
53
57
|
}
|
|
54
|
-
async
|
|
55
|
-
const html = await this.templateRenderer.render(template.content,
|
|
58
|
+
async sendMail(templateId, template, variables) {
|
|
59
|
+
const html = await this.templateRenderer.render(template.content, variables);
|
|
56
60
|
await this.mailer.send({
|
|
57
|
-
to:
|
|
58
|
-
subject: await this.templateRenderer.render(template.subject,
|
|
61
|
+
to: variables.email,
|
|
62
|
+
subject: await this.templateRenderer.render(template.subject, variables),
|
|
59
63
|
html,
|
|
64
|
+
variables,
|
|
65
|
+
template: templateId,
|
|
60
66
|
...template.replyTo ? {
|
|
61
67
|
replyTo: template.replyTo
|
|
62
68
|
} : {}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"UserMailer.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/mailing/UserMailer.ts"],"sourcesContent":["import { Mailer, TemplateRenderer } from '../../utils'\nimport NewUserInvited from './templates/NewUserInvited.mustache'\nimport ExistingUserInvited from './templates/ExistingUserInvited.mustache'\nimport PasswordReset from './templates/PasswordReset.mustache'\nimport PasswordlessSignIn from './templates/PasswordlessSignIn.mustache'\nimport { MailTemplateData, MailTemplateIdentifier, MailType } from './type'\nimport { MailTemplateQuery } from '../queries'\nimport Layout from './templates/Layout.mustache'\nimport { DatabaseContext } from '../utils'\n\nexport class UserMailer {\n\tconstructor(\n\t\tprivate readonly mailer: Mailer,\n\t\tprivate readonly templateRenderer: TemplateRenderer,\n\t) { }\n\n\tasync sendNewUserInvitedMail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; password: string | null; token: string | null; project: string; projectSlug: string },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst
|
|
1
|
+
{"version":3,"file":"UserMailer.cjs","sources":["../../../../../../packages/engine-tenant-api/src/model/mailing/UserMailer.ts"],"sourcesContent":["import { Mailer, TemplateRenderer } from '../../utils'\nimport NewUserInvited from './templates/NewUserInvited.mustache'\nimport ExistingUserInvited from './templates/ExistingUserInvited.mustache'\nimport PasswordReset from './templates/PasswordReset.mustache'\nimport PasswordlessSignIn from './templates/PasswordlessSignIn.mustache'\nimport { MailTemplateData, MailTemplateIdentifier, MailType } from './type'\nimport { MailTemplateQuery } from '../queries'\nimport Layout from './templates/Layout.mustache'\nimport { DatabaseContext } from '../utils'\nimport { Acl } from '@contember/schema'\n\nexport class UserMailer {\n\tconstructor(\n\t\tprivate readonly mailer: Mailer,\n\t\tprivate readonly templateRenderer: TemplateRenderer,\n\t) { }\n\n\tasync sendNewUserInvitedMail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; password: string | null; token: string | null; project: string; projectSlug: string; memberships: readonly Acl.Membership[]},\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.newUserInvited, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: NewUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendExistingUserInvitedEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; project: string; projectSlug: string; memberships: readonly Acl.Membership[] },\n\t\tcustomMailOptions: { projectId: string; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.existingUserInvited, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'You have been invited to {{project}}',\n\t\t\tcontent: ExistingUserInvited,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendPasswordResetEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.passwordReset, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'Password reset',\n\t\t\tcontent: PasswordReset,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tasync sendPasswordlessEmail(\n\t\tdbContext: DatabaseContext,\n\t\tmailArguments: { email: string; token: string; project?: string; projectSlug?: string; url?: string },\n\t\tcustomMailOptions: { projectId: string | null; variant: string },\n\t): Promise<void> {\n\t\tconst templateId = { type: MailType.passwordlessSignIn, ...customMailOptions }\n\t\tconst template = (await this.getCustomTemplate(dbContext, templateId)) || {\n\t\t\tsubject: 'Sign in here',\n\t\t\tcontent: PasswordlessSignIn,\n\t\t\treplyTo: null,\n\t\t}\n\t\tawait this.sendMail(templateId, template, mailArguments)\n\t}\n\n\tprivate async sendMail(\n\t\ttemplateId: MailTemplateIdentifier,\n\t\ttemplate: Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'>,\n\t\tvariables: Record<string, any>,\n\t) {\n\t\tconst html = await this.templateRenderer.render(template.content, variables)\n\t\tawait this.mailer.send({\n\t\t\tto: variables.email,\n\t\t\tsubject: await this.templateRenderer.render(template.subject, variables),\n\t\t\thtml,\n\t\t\tvariables,\n\t\t\ttemplate: templateId,\n\t\t\t...(template.replyTo ? {\n\t\t\t\treplyTo: template.replyTo,\n\t\t\t} : {}),\n\t\t})\n\t}\n\n\tprivate async getCustomTemplate(\n\t\tdbContext: DatabaseContext,\n\t\tidentifier: MailTemplateIdentifier,\n\t): Promise<Pick<MailTemplateData, 'subject' | 'content' | 'replyTo'> | null> {\n\t\tconst customTemplate =\n\t\t\t(await dbContext.queryHandler.fetch(new MailTemplateQuery(identifier)))\n\t\t\t?? (await dbContext.queryHandler.fetch(new MailTemplateQuery({ ...identifier, projectId: null })))\n\n\t\tif (!customTemplate) {\n\t\t\treturn null\n\t\t}\n\t\tconst content = customTemplate.useLayout ? Layout(customTemplate.content) : customTemplate.content\n\t\treturn { content, subject: customTemplate.subject, replyTo: customTemplate.replyTo }\n\t}\n}\n"],"names":["MailType","NewUserInvited","ExistingUserInvited","PasswordReset","PasswordlessSignIn","MailTemplateQuery","Layout"],"mappings":";;;;;;;;;;;;;;;;AAWO,MAAM,WAAW;AAAA,EACvB,YACkB,QACA,kBAChB;AAFgB,SAAA,SAAA;AACA,SAAA,mBAAA;AAAA,EAAA;AAAA,EAGlB,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAMA,KAAS,SAAA,gBAAgB,GAAG,kBAAkB;AACzE,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAASC;AAAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,6BACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAMD,KAAS,SAAA,qBAAqB,GAAG,kBAAkB;AAC9E,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAASE;AAAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,uBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAMF,KAAS,SAAA,eAAe,GAAG,kBAAkB;AACxE,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAASG;AAAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAM,sBACL,WACA,eACA,mBACgB;AAChB,UAAM,aAAa,EAAE,MAAMH,KAAS,SAAA,oBAAoB,GAAG,kBAAkB;AAC7E,UAAM,WAAY,MAAM,KAAK,kBAAkB,WAAW,UAAU,KAAM;AAAA,MACzE,SAAS;AAAA,MACT,SAASI;AAAAA,MACT,SAAS;AAAA,IACV;AACA,UAAM,KAAK,SAAS,YAAY,UAAU,aAAa;AAAA,EAAA;AAAA,EAGxD,MAAc,SACb,YACA,UACA,WACC;AACD,UAAM,OAAO,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,SAAS;AACrE,UAAA,KAAK,OAAO,KAAK;AAAA,MACtB,IAAI,UAAU;AAAA,MACd,SAAS,MAAM,KAAK,iBAAiB,OAAO,SAAS,SAAS,SAAS;AAAA,MACvE;AAAA,MACA;AAAA,MACA,UAAU;AAAA,MACV,GAAI,SAAS,UAAU;AAAA,QACtB,SAAS,SAAS;AAAA,MAAA,IACf,CAAA;AAAA,IAAC,CACL;AAAA,EAAA;AAAA,EAGF,MAAc,kBACb,WACA,YAC4E;AACtE,UAAA,iBACJ,MAAM,UAAU,aAAa,MAAM,IAAIC,kBAAA,kBAAkB,UAAU,CAAC,KACjE,MAAM,UAAU,aAAa,MAAM,IAAIA,oCAAkB,EAAE,GAAG,YAAY,WAAW,KAAK,CAAC,CAAC;AAEjG,QAAI,CAAC,gBAAgB;AACb,aAAA;AAAA,IAAA;AAER,UAAM,UAAU,eAAe,YAAYC,gBAAO,eAAe,OAAO,IAAI,eAAe;AAC3F,WAAO,EAAE,SAAS,SAAS,eAAe,SAAS,SAAS,eAAe,QAAQ;AAAA,EAAA;AAErF;;"}
|