@adminforth/email-invite 1.2.2 → 1.2.4
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/build.log +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +16 -5
- package/index.ts +20 -7
- package/package.json +1 -1
package/build.log
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
10
10
|
modifyResourceConfig(adminforth: IAdminForth, resourceConfig: AdminForthResource): Promise<void>;
|
|
11
11
|
validateConfigAfterDiscover(adminforth: IAdminForth, resourceConfig: AdminForthResource): void;
|
|
12
12
|
instanceUniqueRepresentation(pluginOptions: any): string;
|
|
13
|
+
passwordHashDigest(passwordHash: string | null | undefined): string;
|
|
13
14
|
handleUserCreation({ resource, adminUser, record, adminforth, extra }: {
|
|
14
15
|
resource: AdminForthResource;
|
|
15
16
|
adminUser: AdminUser;
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
9
9
|
};
|
|
10
10
|
import AdminForth, { AdminForthPlugin, suggestIfTypo, Filters, afLogger } from "adminforth";
|
|
11
11
|
import { z } from "zod";
|
|
12
|
+
import { createHash, randomUUID } from "crypto";
|
|
12
13
|
const setPasswordBodySchema = z.object({
|
|
13
14
|
token: z.string(),
|
|
14
15
|
password: z.string(),
|
|
@@ -119,6 +120,9 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
119
120
|
instanceUniqueRepresentation(pluginOptions) {
|
|
120
121
|
return `single`;
|
|
121
122
|
}
|
|
123
|
+
passwordHashDigest(passwordHash) {
|
|
124
|
+
return createHash('sha256').update(passwordHash || '').digest('hex').slice(0, 16);
|
|
125
|
+
}
|
|
122
126
|
handleUserCreation(_a) {
|
|
123
127
|
return __awaiter(this, arguments, void 0, function* ({ resource, adminUser, record, adminforth, extra }) {
|
|
124
128
|
try {
|
|
@@ -127,7 +131,7 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
127
131
|
delete record.password;
|
|
128
132
|
}
|
|
129
133
|
const passwordHashFieldName = adminforth.config.auth.passwordHashField;
|
|
130
|
-
record[passwordHashFieldName] = yield AdminForth.Utils.generatePasswordHash('TEMP_INVITE_PLACEHOLDER_' +
|
|
134
|
+
record[passwordHashFieldName] = yield AdminForth.Utils.generatePasswordHash('TEMP_INVITE_PLACEHOLDER_' + randomUUID());
|
|
131
135
|
if (this.options.emailConfirmedField && this.emailConfirmedField) {
|
|
132
136
|
record[this.emailConfirmedField.name] = false;
|
|
133
137
|
}
|
|
@@ -144,15 +148,19 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
144
148
|
var _b, _c;
|
|
145
149
|
try {
|
|
146
150
|
const email = record[this.options.emailField];
|
|
151
|
+
const newRecordId = record[this.authResource.columns.find(col => col.primaryKey).name];
|
|
147
152
|
if (!email || !/\S+@\S+\.\S+/.test(email)) {
|
|
148
153
|
console.warn('Email invite plugin: Invalid or missing email address');
|
|
149
154
|
return { ok: true }; // Don't fail user creation for email issues
|
|
150
155
|
}
|
|
151
156
|
const brandName = adminforth.config.customization.brandName || 'Admin Panel';
|
|
152
|
-
const inviteToken = adminforth.auth.issueJWT({
|
|
157
|
+
const inviteToken = adminforth.auth.issueJWT({
|
|
158
|
+
email,
|
|
159
|
+
recordId: newRecordId,
|
|
160
|
+
inviteEmail: true,
|
|
161
|
+
ph: this.passwordHashDigest(record[this.adminforth.config.auth.passwordHashField])
|
|
162
|
+
}, 'inviteUser', '7d' // 7 days validity
|
|
153
163
|
);
|
|
154
|
-
console.log('Sending invite email to:', email);
|
|
155
|
-
console.log('Generated JWT token payload:', { email, recordId, inviteEmail: true });
|
|
156
164
|
const host = ((_b = extra === null || extra === void 0 ? void 0 : extra.headers) === null || _b === void 0 ? void 0 : _b.host) || 'localhost';
|
|
157
165
|
const protocol = ((_c = extra === null || extra === void 0 ? void 0 : extra.headers) === null || _c === void 0 ? void 0 : _c['x-forwarded-proto']) || 'http';
|
|
158
166
|
const inviteUrl = `${protocol}://${host}/set-password?token=${inviteToken}`;
|
|
@@ -219,6 +227,9 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
219
227
|
if (recordId) {
|
|
220
228
|
userRecord = yield this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.authResource.columns.find(c => c.primaryKey).name, recordId));
|
|
221
229
|
}
|
|
230
|
+
if (decoded.ph !== this.passwordHashDigest(userRecord[this.adminforth.config.auth.passwordHashField])) {
|
|
231
|
+
return { error: 'Token has already been used', ok: false };
|
|
232
|
+
}
|
|
222
233
|
if (!userRecord && email) {
|
|
223
234
|
const records = yield this.adminforth.resource(this.authResource.resourceId).list(Filters.EQ(this.options.emailField, email), 1);
|
|
224
235
|
userRecord = records.length > 0 ? records[0] : null;
|
|
@@ -226,7 +237,7 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
226
237
|
if (!userRecord) {
|
|
227
238
|
return { error: 'User not found', ok: false };
|
|
228
239
|
}
|
|
229
|
-
if (userRecord[this.options.emailConfirmedField]) {
|
|
240
|
+
if (this.options.emailConfirmedField && userRecord[this.options.emailConfirmedField]) {
|
|
230
241
|
return { error: 'Password already set. Invitation link cannot be reused.', ok: false };
|
|
231
242
|
}
|
|
232
243
|
const userEmail = userRecord[this.options.emailField];
|
package/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import AdminForth, { AdminForthPlugin, suggestIfTypo, Filters, afLogger } from "
|
|
|
2
2
|
import type { IAdminForth, IHttpServer, AdminForthResourcePages, AdminForthResourceColumn, AdminForthDataTypes, AdminForthResource, AdminUser, HttpExtra } from "adminforth";
|
|
3
3
|
import type { PluginOptions } from './types.js';
|
|
4
4
|
import { z } from "zod";
|
|
5
|
+
import { createHash, randomUUID } from "crypto";
|
|
5
6
|
|
|
6
7
|
const setPasswordBodySchema = z.object({
|
|
7
8
|
token: z.string(),
|
|
@@ -137,6 +138,11 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
137
138
|
return `single`;
|
|
138
139
|
}
|
|
139
140
|
|
|
141
|
+
passwordHashDigest(passwordHash: string | null | undefined): string {
|
|
142
|
+
return createHash('sha256').update(passwordHash || '').digest('hex').slice(0, 16);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
|
|
140
146
|
async handleUserCreation({ resource, adminUser, record, adminforth, extra }: {
|
|
141
147
|
resource: AdminForthResource;
|
|
142
148
|
adminUser: AdminUser;
|
|
@@ -151,7 +157,7 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
151
157
|
}
|
|
152
158
|
|
|
153
159
|
const passwordHashFieldName = adminforth.config.auth!.passwordHashField;
|
|
154
|
-
record[passwordHashFieldName] = await AdminForth.Utils.generatePasswordHash('TEMP_INVITE_PLACEHOLDER_' +
|
|
160
|
+
record[passwordHashFieldName] = await AdminForth.Utils.generatePasswordHash('TEMP_INVITE_PLACEHOLDER_' + randomUUID());
|
|
155
161
|
|
|
156
162
|
if (this.options.emailConfirmedField && this.emailConfirmedField) {
|
|
157
163
|
record[this.emailConfirmedField.name] = false;
|
|
@@ -174,6 +180,7 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
174
180
|
}): Promise<{ok: boolean, error?: string}> {
|
|
175
181
|
try {
|
|
176
182
|
const email = record[this.options.emailField];
|
|
183
|
+
const newRecordId = record[this.authResource.columns.find(col => col.primaryKey)!.name];
|
|
177
184
|
|
|
178
185
|
if (!email || !/\S+@\S+\.\S+/.test(email)) {
|
|
179
186
|
console.warn('Email invite plugin: Invalid or missing email address');
|
|
@@ -181,16 +188,18 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
181
188
|
}
|
|
182
189
|
|
|
183
190
|
const brandName = adminforth.config.customization.brandName || 'Admin Panel';
|
|
184
|
-
|
|
191
|
+
|
|
185
192
|
const inviteToken = adminforth.auth.issueJWT(
|
|
186
|
-
{
|
|
193
|
+
{
|
|
194
|
+
email,
|
|
195
|
+
recordId: newRecordId,
|
|
196
|
+
inviteEmail: true,
|
|
197
|
+
ph: this.passwordHashDigest(record[this.adminforth.config.auth.passwordHashField])
|
|
198
|
+
},
|
|
187
199
|
'inviteUser',
|
|
188
200
|
'7d' // 7 days validity
|
|
189
201
|
);
|
|
190
202
|
|
|
191
|
-
console.log('Sending invite email to:', email);
|
|
192
|
-
console.log('Generated JWT token payload:', { email, recordId, inviteEmail: true });
|
|
193
|
-
|
|
194
203
|
const host = extra?.headers?.host || 'localhost';
|
|
195
204
|
const protocol = extra?.headers?.['x-forwarded-proto'] || 'http';
|
|
196
205
|
const inviteUrl = `${protocol}://${host}/set-password?token=${inviteToken}`;
|
|
@@ -270,6 +279,10 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
270
279
|
userRecord = await this.adminforth.resource(this.authResource.resourceId).get(Filters.EQ(this.authResource.columns.find(c => c.primaryKey).name, recordId));
|
|
271
280
|
}
|
|
272
281
|
|
|
282
|
+
if (decoded.ph !== this.passwordHashDigest(userRecord[this.adminforth.config.auth.passwordHashField])) {
|
|
283
|
+
return { error: 'Token has already been used', ok: false };
|
|
284
|
+
}
|
|
285
|
+
|
|
273
286
|
if (!userRecord && email) {
|
|
274
287
|
const records = await this.adminforth.resource(this.authResource.resourceId).list(
|
|
275
288
|
Filters.EQ(this.options.emailField, email),
|
|
@@ -281,7 +294,7 @@ export default class EmailInvitePlugin extends AdminForthPlugin {
|
|
|
281
294
|
return { error: 'User not found', ok: false };
|
|
282
295
|
}
|
|
283
296
|
|
|
284
|
-
if ( userRecord[this.options.emailConfirmedField] ) {
|
|
297
|
+
if ( this.options.emailConfirmedField && userRecord[this.options.emailConfirmedField] ) {
|
|
285
298
|
return { error: 'Password already set. Invitation link cannot be reused.', ok: false };
|
|
286
299
|
}
|
|
287
300
|
const userEmail = userRecord[this.options.emailField];
|