@adminforth/email-invite 1.0.1

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.
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "baseUrl": ".", // This should point to your project root
4
+ "paths": {
5
+ "@/*": [
6
+ // "node_modules/adminforth/dist/spa/src/*"
7
+ "../../../spa/src/*"
8
+ ],
9
+ "*": [
10
+ // "node_modules/adminforth/dist/spa/node_modules/*"
11
+ "../../../spa/node_modules/*"
12
+ ],
13
+ "@@/*": [
14
+ // "node_modules/adminforth/dist/spa/src/*"
15
+ "."
16
+ ]
17
+ }
18
+ }
19
+ }
package/dist/index.js ADDED
@@ -0,0 +1,269 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import AdminForth, { AdminForthPlugin, suggestIfTypo, Filters } from "adminforth";
11
+ export default class EmailInvitePlugin extends AdminForthPlugin {
12
+ constructor(options) {
13
+ super(options, import.meta.url);
14
+ this.options = options;
15
+ }
16
+ modifyResourceConfig(adminforth, resourceConfig) {
17
+ const _super = Object.create(null, {
18
+ modifyResourceConfig: { get: () => super.modifyResourceConfig }
19
+ });
20
+ return __awaiter(this, void 0, void 0, function* () {
21
+ _super.modifyResourceConfig.call(this, adminforth, resourceConfig);
22
+ if (!this.options.emailField) {
23
+ throw new Error(`emailField is required and should be a name of field in auth resource`);
24
+ }
25
+ if (!this.options.sendFrom) {
26
+ throw new Error(`sendFrom is required and should be a valid email address`);
27
+ }
28
+ if (!this.options.adapter) {
29
+ throw new Error('Adapter is required. Please provide a valid email adapter in the plugin options.');
30
+ }
31
+ if (!adminforth.config.auth) {
32
+ throw new Error('Auth configuration is required for email invite plugin');
33
+ }
34
+ const authResource = adminforth.config.resources.find(r => r.resourceId === adminforth.config.auth.usersResourceId);
35
+ if (!authResource) {
36
+ throw new Error(`Resource with id config.auth.usersResourceId=${adminforth.config.auth.usersResourceId} not found`);
37
+ }
38
+ this.authResource = authResource;
39
+ const emailField = authResource.columns.find(f => f.name === this.options.emailField);
40
+ if (!emailField) {
41
+ const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.emailField);
42
+ throw new Error(`Field with name ${this.options.emailField} not found in resource ${authResource.resourceId}.
43
+ ${similar ? `Did you mean ${similar}?` : ''}
44
+ `);
45
+ }
46
+ this.emailField = emailField;
47
+ if (!this.options.passwordField) {
48
+ throw new Error(`passwordField is required to get password constraints and should be a name of virtual field in auth resource`);
49
+ }
50
+ const passwordField = authResource.columns.find(f => f.name === this.options.passwordField);
51
+ if (!passwordField) {
52
+ const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.passwordField);
53
+ throw new Error(`Field with name ${this.options.passwordField} not found in resource ${authResource.resourceId}.
54
+ ${similar ? `Did you mean ${similar}?` : ''}
55
+ `);
56
+ }
57
+ if (this.options.emailConfirmedField) {
58
+ const emailConfirmedField = authResource.columns.find(f => f.name === this.options.emailConfirmedField);
59
+ if (!emailConfirmedField) {
60
+ const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.emailConfirmedField);
61
+ throw new Error(`Email confirmed field with name ${this.options.emailConfirmedField} not found in resource ${authResource.resourceId}.
62
+ ${similar ? `Did you mean ${similar}?` : ''}
63
+ `);
64
+ }
65
+ this.emailConfirmedField = emailConfirmedField;
66
+ }
67
+ if (!authResource.hooks) {
68
+ authResource.hooks = {};
69
+ }
70
+ if (!authResource.hooks.create) {
71
+ authResource.hooks.create = {};
72
+ }
73
+ if (!authResource.hooks.create.beforeSave) {
74
+ authResource.hooks.create.beforeSave = [];
75
+ }
76
+ if (!Array.isArray(authResource.hooks.create.beforeSave)) {
77
+ authResource.hooks.create.beforeSave = [authResource.hooks.create.beforeSave];
78
+ }
79
+ authResource.hooks.create.beforeSave.push(this.handleUserCreation.bind(this));
80
+ if (!authResource.hooks.create.afterSave) {
81
+ authResource.hooks.create.afterSave = [];
82
+ }
83
+ if (!Array.isArray(authResource.hooks.create.afterSave)) {
84
+ authResource.hooks.create.afterSave = [authResource.hooks.create.afterSave];
85
+ }
86
+ authResource.hooks.create.afterSave.push(this.sendInviteEmail.bind(this));
87
+ adminforth.config.customization.customPages.push({
88
+ path: '/set-password',
89
+ component: {
90
+ file: this.componentPath('SetPassword.vue'),
91
+ meta: {
92
+ customLayout: true,
93
+ pluginInstanceId: this.pluginInstanceId,
94
+ passwordField: {
95
+ minLength: passwordField.minLength,
96
+ maxLength: passwordField.maxLength,
97
+ validation: passwordField.validation
98
+ }
99
+ }
100
+ }
101
+ });
102
+ });
103
+ }
104
+ validateConfigAfterDiscover(adminforth, resourceConfig) {
105
+ this.options.adapter.validate();
106
+ }
107
+ instanceUniqueRepresentation(pluginOptions) {
108
+ return `single`;
109
+ }
110
+ handleUserCreation(_a) {
111
+ return __awaiter(this, arguments, void 0, function* ({ resource, adminUser, record, adminforth, extra }) {
112
+ try {
113
+ // Remove any password field from record since users will set it via invite
114
+ if ('password' in record) {
115
+ delete record.password;
116
+ }
117
+ const passwordHashFieldName = adminforth.config.auth.passwordHashField;
118
+ record[passwordHashFieldName] = yield AdminForth.Utils.generatePasswordHash('TEMP_INVITE_PLACEHOLDER_' + Date.now());
119
+ if (this.options.emailConfirmedField && this.emailConfirmedField) {
120
+ record[this.emailConfirmedField.name] = false;
121
+ }
122
+ return { ok: true };
123
+ }
124
+ catch (error) {
125
+ console.error('Error in handleUserCreation:', error);
126
+ return { ok: false, error: 'Failed to prepare user for creation' };
127
+ }
128
+ });
129
+ }
130
+ sendInviteEmail(_a) {
131
+ return __awaiter(this, arguments, void 0, function* ({ recordId, resource, record, adminUser, adminforth, extra }) {
132
+ var _b, _c;
133
+ try {
134
+ const email = record[this.options.emailField];
135
+ if (!email || !/\S+@\S+\.\S+/.test(email)) {
136
+ console.warn('Email invite plugin: Invalid or missing email address');
137
+ return { ok: true }; // Don't fail user creation for email issues
138
+ }
139
+ const brandName = adminforth.config.customization.brandName || 'Admin Panel';
140
+ const inviteToken = adminforth.auth.issueJWT({ email, recordId, inviteEmail: true }, 'inviteUser', '7d' // 7 days validity
141
+ );
142
+ console.log('Sending invite email to:', email);
143
+ console.log('Generated JWT token payload:', { email, recordId, inviteEmail: true });
144
+ const host = ((_b = extra === null || extra === void 0 ? void 0 : extra.headers) === null || _b === void 0 ? void 0 : _b.host) || 'localhost';
145
+ const protocol = ((_c = extra === null || extra === void 0 ? void 0 : extra.headers) === null || _c === void 0 ? void 0 : _c['x-forwarded-proto']) || 'http';
146
+ const inviteUrl = `${protocol}://${host}/set-password?token=${inviteToken}`;
147
+ const emailSubject = `You're invited to ${brandName}`;
148
+ const emailText = `
149
+ Dear user,
150
+
151
+ You have been invited to join ${brandName}. To complete your registration and set your password, click the link below:
152
+
153
+ ${inviteUrl}
154
+
155
+ If you didn't expect this invitation, please ignore this email.
156
+ Link is valid for 7 days.
157
+
158
+ Thanks,
159
+ The ${brandName} Team
160
+ `;
161
+ const emailHtml = `
162
+ <html>
163
+ <head></head>
164
+ <body>
165
+ <p>Dear user,</p>
166
+ <p>You have been invited to join ${brandName}. To complete your registration and set your password, click the link below:</p>
167
+ <p><a href="${inviteUrl}">Set your password</a></p>
168
+ <p>If you didn't expect this invitation, please ignore this email.</p>
169
+ <p>Link is valid for 7 days.</p>
170
+ <p>Thanks,</p>
171
+ <p>The ${brandName} Team</p>
172
+ </body>
173
+ </html>
174
+ `;
175
+ // Send email
176
+ const result = yield this.options.adapter.sendEmail(this.options.sendFrom, email, emailText, emailHtml, emailSubject);
177
+ if (result && result.error) {
178
+ console.error('Failed to send invite email:', result.error);
179
+ }
180
+ else {
181
+ console.log('Invite email sent successfully to:', email);
182
+ }
183
+ return { ok: true };
184
+ }
185
+ catch (error) {
186
+ console.error('Error sending invite email:', error);
187
+ return { ok: true };
188
+ }
189
+ });
190
+ }
191
+ setupEndpoints(server) {
192
+ server.endpoint({
193
+ method: 'POST',
194
+ path: `/plugin/${this.pluginInstanceId}/set-password`,
195
+ noAuth: true,
196
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body }) {
197
+ const { token, password } = body;
198
+ try {
199
+ const decoded = yield this.adminforth.auth.verify(token, 'inviteUser', false);
200
+ if (!decoded || !decoded.inviteEmail) {
201
+ return { error: 'Invalid or expired invitation token', ok: false };
202
+ }
203
+ const { email, recordId } = decoded;
204
+ let userRecord;
205
+ if (recordId) {
206
+ userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(recordId);
207
+ }
208
+ if (!userRecord && email) {
209
+ const records = yield this.adminforth.resource(this.authResource.resourceId).list(Filters.EQ(this.options.emailField, email), 1);
210
+ userRecord = records.length > 0 ? records[0] : null;
211
+ }
212
+ if (!userRecord) {
213
+ return { error: 'User not found', ok: false };
214
+ }
215
+ const userEmail = userRecord[this.options.emailField];
216
+ const tokenEmail = email;
217
+ if (!userEmail || userEmail.toLowerCase() !== tokenEmail.toLowerCase()) {
218
+ return { error: 'Email mismatch', ok: false };
219
+ }
220
+ const passwordHashFieldName = this.adminforth.config.auth.passwordHashField;
221
+ const newPasswordHash = yield AdminForth.Utils.generatePasswordHash(password);
222
+ const primaryKeyField = this.authResource.columns.find(c => c.primaryKey);
223
+ const userRecordId = userRecord[primaryKeyField.name];
224
+ const updateData = {
225
+ [passwordHashFieldName]: newPasswordHash
226
+ };
227
+ if (this.options.emailConfirmedField && this.emailConfirmedField) {
228
+ updateData[this.emailConfirmedField.name] = true;
229
+ }
230
+ yield this.adminforth.resource(this.authResource.resourceId).update(userRecordId, updateData);
231
+ console.log('Password set successfully for user:', email);
232
+ return { ok: true };
233
+ }
234
+ catch (error) {
235
+ console.error('Error setting password:', error);
236
+ return { error: 'Failed to set password', ok: false };
237
+ }
238
+ })
239
+ });
240
+ server.endpoint({
241
+ method: 'POST',
242
+ path: `/plugin/${this.pluginInstanceId}/resend-invite`,
243
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, extra }) {
244
+ const { recordId } = body;
245
+ try {
246
+ // Get the user record
247
+ const userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(recordId);
248
+ if (!userRecord) {
249
+ return { error: 'User not found', ok: false };
250
+ }
251
+ // Simulate the afterSave hook call to resend invite
252
+ yield this.sendInviteEmail({
253
+ recordId,
254
+ resource: this.authResource,
255
+ record: userRecord,
256
+ adminUser,
257
+ adminforth: this.adminforth,
258
+ extra
259
+ });
260
+ return { ok: true };
261
+ }
262
+ catch (error) {
263
+ console.error('Error resending invite:', error);
264
+ return { error: 'Failed to resend invitation', ok: false };
265
+ }
266
+ })
267
+ });
268
+ }
269
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/index.ts ADDED
@@ -0,0 +1,331 @@
1
+ import AdminForth, { AdminForthPlugin, suggestIfTypo, Filters } from "adminforth";
2
+ import type { IAdminForth, IHttpServer, AdminForthResourcePages, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource, AdminUser, HttpExtra } from "adminforth";
3
+ import type { PluginOptions } from './types.js';
4
+
5
+ export default class EmailInvitePlugin extends AdminForthPlugin {
6
+ options: PluginOptions;
7
+ authResource!: AdminForthResource;
8
+ emailField!: AdminForthResourceColumn;
9
+ emailConfirmedField?: AdminForthResourceColumn;
10
+
11
+ constructor(options: PluginOptions) {
12
+ super(options, import.meta.url);
13
+ this.options = options;
14
+ }
15
+
16
+ async modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
17
+ super.modifyResourceConfig(adminforth, resourceConfig);
18
+
19
+ if (!this.options.emailField) {
20
+ throw new Error(`emailField is required and should be a name of field in auth resource`);
21
+ }
22
+
23
+ if (!this.options.sendFrom) {
24
+ throw new Error(`sendFrom is required and should be a valid email address`);
25
+ }
26
+
27
+ if (!this.options.adapter) {
28
+ throw new Error('Adapter is required. Please provide a valid email adapter in the plugin options.');
29
+ }
30
+
31
+ if (!adminforth.config.auth) {
32
+ throw new Error('Auth configuration is required for email invite plugin');
33
+ }
34
+
35
+ const authResource = adminforth.config.resources.find(r => r.resourceId === adminforth.config.auth!.usersResourceId);
36
+ if (!authResource) {
37
+ throw new Error(`Resource with id config.auth.usersResourceId=${adminforth.config.auth!.usersResourceId} not found`);
38
+ }
39
+ this.authResource = authResource;
40
+
41
+ const emailField = authResource.columns.find(f => f.name === this.options.emailField);
42
+ if (!emailField) {
43
+ const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.emailField);
44
+
45
+ throw new Error(`Field with name ${this.options.emailField} not found in resource ${authResource.resourceId}.
46
+ ${similar ? `Did you mean ${similar}?` : ''}
47
+ `);
48
+ }
49
+ this.emailField = emailField;
50
+
51
+ if (!this.options.passwordField) {
52
+ throw new Error(`passwordField is required to get password constraints and should be a name of virtual field in auth resource`);
53
+ }
54
+
55
+ const passwordField = authResource.columns.find(f => f.name === this.options.passwordField);
56
+ if (!passwordField) {
57
+ const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.passwordField);
58
+
59
+ throw new Error(`Field with name ${this.options.passwordField} not found in resource ${authResource.resourceId}.
60
+ ${similar ? `Did you mean ${similar}?` : ''}
61
+ `);
62
+ }
63
+
64
+ if (this.options.emailConfirmedField) {
65
+ const emailConfirmedField = authResource.columns.find(f => f.name === this.options.emailConfirmedField);
66
+ if (!emailConfirmedField) {
67
+ const similar = suggestIfTypo(authResource.columns.map(f => f.name), this.options.emailConfirmedField);
68
+
69
+ throw new Error(`Email confirmed field with name ${this.options.emailConfirmedField} not found in resource ${authResource.resourceId}.
70
+ ${similar ? `Did you mean ${similar}?` : ''}
71
+ `);
72
+ }
73
+ this.emailConfirmedField = emailConfirmedField;
74
+ }
75
+
76
+ if (!authResource.hooks) {
77
+ authResource.hooks = {};
78
+ }
79
+ if (!authResource.hooks.create) {
80
+ authResource.hooks.create = {};
81
+ }
82
+
83
+ if (!authResource.hooks.create.beforeSave) {
84
+ authResource.hooks.create.beforeSave = [];
85
+ }
86
+ if (!Array.isArray(authResource.hooks.create.beforeSave)) {
87
+ authResource.hooks.create.beforeSave = [authResource.hooks.create.beforeSave];
88
+ }
89
+ authResource.hooks.create.beforeSave.push(this.handleUserCreation.bind(this));
90
+
91
+ if (!authResource.hooks.create.afterSave) {
92
+ authResource.hooks.create.afterSave = [];
93
+ }
94
+ if (!Array.isArray(authResource.hooks.create.afterSave)) {
95
+ authResource.hooks.create.afterSave = [authResource.hooks.create.afterSave];
96
+ }
97
+ authResource.hooks.create.afterSave.push(this.sendInviteEmail.bind(this));
98
+
99
+ adminforth.config.customization.customPages.push({
100
+ path: '/set-password',
101
+ component: {
102
+ file: this.componentPath('SetPassword.vue'),
103
+ meta: {
104
+ customLayout: true,
105
+ pluginInstanceId: this.pluginInstanceId,
106
+ passwordField: {
107
+ minLength: passwordField.minLength,
108
+ maxLength: passwordField.maxLength,
109
+ validation: passwordField.validation
110
+ }
111
+ }
112
+ }
113
+ });
114
+ }
115
+
116
+ validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource) {
117
+ this.options.adapter.validate();
118
+ }
119
+
120
+ instanceUniqueRepresentation(pluginOptions: any): string {
121
+ return `single`;
122
+ }
123
+
124
+ async handleUserCreation({ resource, adminUser, record, adminforth, extra }: {
125
+ resource: AdminForthResource;
126
+ adminUser: AdminUser;
127
+ record: any;
128
+ adminforth: IAdminForth;
129
+ extra?: HttpExtra;
130
+ }): Promise<{ok: boolean, error?: string}> {
131
+ try {
132
+ // Remove any password field from record since users will set it via invite
133
+ if ('password' in record) {
134
+ delete record.password;
135
+ }
136
+
137
+ const passwordHashFieldName = adminforth.config.auth!.passwordHashField;
138
+ record[passwordHashFieldName] = await AdminForth.Utils.generatePasswordHash('TEMP_INVITE_PLACEHOLDER_' + Date.now());
139
+
140
+ if (this.options.emailConfirmedField && this.emailConfirmedField) {
141
+ record[this.emailConfirmedField.name] = false;
142
+ }
143
+
144
+ return { ok: true };
145
+ } catch (error) {
146
+ console.error('Error in handleUserCreation:', error);
147
+ return { ok: false, error: 'Failed to prepare user for creation' };
148
+ }
149
+ }
150
+
151
+ async sendInviteEmail({ recordId, resource, record, adminUser, adminforth, extra }: {
152
+ recordId: any;
153
+ resource: AdminForthResource;
154
+ record: any;
155
+ adminUser: AdminUser;
156
+ adminforth: IAdminForth;
157
+ extra?: HttpExtra;
158
+ }): Promise<{ok: boolean, error?: string}> {
159
+ try {
160
+ const email = record[this.options.emailField];
161
+
162
+ if (!email || !/\S+@\S+\.\S+/.test(email)) {
163
+ console.warn('Email invite plugin: Invalid or missing email address');
164
+ return { ok: true }; // Don't fail user creation for email issues
165
+ }
166
+
167
+ const brandName = adminforth.config.customization.brandName || 'Admin Panel';
168
+
169
+ const inviteToken = adminforth.auth.issueJWT(
170
+ { email, recordId, inviteEmail: true },
171
+ 'inviteUser',
172
+ '7d' // 7 days validity
173
+ );
174
+
175
+ console.log('Sending invite email to:', email);
176
+ console.log('Generated JWT token payload:', { email, recordId, inviteEmail: true });
177
+
178
+ const host = extra?.headers?.host || 'localhost';
179
+ const protocol = extra?.headers?.['x-forwarded-proto'] || 'http';
180
+ const inviteUrl = `${protocol}://${host}/set-password?token=${inviteToken}`;
181
+
182
+ const emailSubject = `You're invited to ${brandName}`;
183
+ const emailText = `
184
+ Dear user,
185
+
186
+ You have been invited to join ${brandName}. To complete your registration and set your password, click the link below:
187
+
188
+ ${inviteUrl}
189
+
190
+ If you didn't expect this invitation, please ignore this email.
191
+ Link is valid for 7 days.
192
+
193
+ Thanks,
194
+ The ${brandName} Team
195
+ `;
196
+
197
+ const emailHtml = `
198
+ <html>
199
+ <head></head>
200
+ <body>
201
+ <p>Dear user,</p>
202
+ <p>You have been invited to join ${brandName}. To complete your registration and set your password, click the link below:</p>
203
+ <p><a href="${inviteUrl}">Set your password</a></p>
204
+ <p>If you didn't expect this invitation, please ignore this email.</p>
205
+ <p>Link is valid for 7 days.</p>
206
+ <p>Thanks,</p>
207
+ <p>The ${brandName} Team</p>
208
+ </body>
209
+ </html>
210
+ `;
211
+
212
+ // Send email
213
+ const result = await this.options.adapter.sendEmail(
214
+ this.options.sendFrom,
215
+ email,
216
+ emailText,
217
+ emailHtml,
218
+ emailSubject
219
+ );
220
+
221
+ if (result && result.error) {
222
+ console.error('Failed to send invite email:', result.error);
223
+ } else {
224
+ console.log('Invite email sent successfully to:', email);
225
+ }
226
+
227
+ return { ok: true };
228
+ } catch (error) {
229
+ console.error('Error sending invite email:', error);
230
+ return { ok: true };
231
+ }
232
+ }
233
+
234
+ setupEndpoints(server: IHttpServer) {
235
+ server.endpoint({
236
+ method: 'POST',
237
+ path: `/plugin/${this.pluginInstanceId}/set-password`,
238
+ noAuth: true,
239
+ handler: async ({ body }) => {
240
+ const { token, password } = body;
241
+
242
+ try {
243
+ const decoded = await this.adminforth.auth.verify(token, 'inviteUser', false);
244
+ if (!decoded || !decoded.inviteEmail) {
245
+ return { error: 'Invalid or expired invitation token', ok: false };
246
+ }
247
+ const { email, recordId } = decoded;
248
+
249
+ let userRecord;
250
+
251
+ if (recordId) {
252
+ userRecord = await this.adminforth.resource(this.authResource.resourceId).get(recordId);
253
+ }
254
+
255
+ if (!userRecord && email) {
256
+ const records = await this.adminforth.resource(this.authResource.resourceId).list(
257
+ Filters.EQ(this.options.emailField, email),
258
+ 1
259
+ );
260
+ userRecord = records.length > 0 ? records[0] : null;
261
+ }
262
+
263
+ if (!userRecord) {
264
+ return { error: 'User not found', ok: false };
265
+ }
266
+
267
+ const userEmail = userRecord[this.options.emailField];
268
+ const tokenEmail = email;
269
+
270
+ if (!userEmail || userEmail.toLowerCase() !== tokenEmail.toLowerCase()) {
271
+ return { error: 'Email mismatch', ok: false };
272
+ }
273
+
274
+ const passwordHashFieldName = this.adminforth.config.auth.passwordHashField;
275
+ const newPasswordHash = await AdminForth.Utils.generatePasswordHash(password);
276
+
277
+ const primaryKeyField = this.authResource.columns.find(c => c.primaryKey);
278
+ const userRecordId = userRecord[primaryKeyField!.name];
279
+
280
+ const updateData: any = {
281
+ [passwordHashFieldName]: newPasswordHash
282
+ };
283
+
284
+ if (this.options.emailConfirmedField && this.emailConfirmedField) {
285
+ updateData[this.emailConfirmedField.name] = true;
286
+ }
287
+
288
+ await this.adminforth.resource(this.authResource.resourceId).update(userRecordId, updateData);
289
+
290
+ console.log('Password set successfully for user:', email);
291
+
292
+ return { ok: true };
293
+ } catch (error) {
294
+ console.error('Error setting password:', error);
295
+ return { error: 'Failed to set password', ok: false };
296
+ }
297
+ }
298
+ });
299
+
300
+ server.endpoint({
301
+ method: 'POST',
302
+ path: `/plugin/${this.pluginInstanceId}/resend-invite`,
303
+ handler: async ({ body, adminUser, extra }) => {
304
+ const { recordId } = body;
305
+
306
+ try {
307
+ // Get the user record
308
+ const userRecord = await this.adminforth.resource(this.authResource.resourceId).get(recordId);
309
+ if (!userRecord) {
310
+ return { error: 'User not found', ok: false };
311
+ }
312
+
313
+ // Simulate the afterSave hook call to resend invite
314
+ await this.sendInviteEmail({
315
+ recordId,
316
+ resource: this.authResource,
317
+ record: userRecord,
318
+ adminUser,
319
+ adminforth: this.adminforth,
320
+ extra
321
+ });
322
+
323
+ return { ok: true };
324
+ } catch (error) {
325
+ console.error('Error resending invite:', error);
326
+ return { error: 'Failed to resend invitation', ok: false };
327
+ }
328
+ }
329
+ });
330
+ }
331
+ }
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@adminforth/email-invite",
3
+ "version": "1.0.1",
4
+ "main": "dist/index.js",
5
+ "types": "dist/index.d.ts",
6
+ "type": "module",
7
+ "homepage": "https://adminforth.dev/docs/tutorial/Plugins/EmailInvite/",
8
+ "scripts": {
9
+ "build": "tsc && rsync -av --exclude 'node_modules' custom dist/",
10
+ "prepare": "npm link adminforth"
11
+ },
12
+ "keywords": [
13
+ "adminforth",
14
+ "email",
15
+ "invite"
16
+ ],
17
+ "author": "devforth",
18
+ "license": "MIT",
19
+ "description": "Email invite plugin for adminforth",
20
+ "devDependencies": {
21
+ "@types/node": "^22.10.7",
22
+ "semantic-release": "^24.2.1",
23
+ "semantic-release-slack-bot": "^4.0.2",
24
+ "typescript": "^5.7.3"
25
+ },
26
+ "dependencies": {
27
+ "adminforth": "latest"
28
+ },
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "release": {
33
+ "plugins": [
34
+ "@semantic-release/commit-analyzer",
35
+ "@semantic-release/release-notes-generator",
36
+ "@semantic-release/npm",
37
+ "@semantic-release/github",
38
+ [
39
+ "semantic-release-slack-bot",
40
+ {
41
+ "notifyOnSuccess": true,
42
+ "notifyOnFail": true,
43
+ "slackIcon": ":package:",
44
+ "markdownReleaseNotes": true
45
+ }
46
+ ]
47
+ ],
48
+ "branches": [
49
+ "main",
50
+ {
51
+ "name": "next",
52
+ "prerelease": true
53
+ }
54
+ ]
55
+ }
56
+ }