Forgot password template moved to notification table
This commit is contained in:
parent
9585bff201
commit
472ccd7ec7
46
scripts/patch-forgot-password-template.sql
Normal file
46
scripts/patch-forgot-password-template.sql
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
-- Forgot password email template (additive; safe if full patch already applied)
|
||||||
|
|
||||||
|
INSERT INTO notification_templates (
|
||||||
|
code,
|
||||||
|
name,
|
||||||
|
channel,
|
||||||
|
subject,
|
||||||
|
html_body,
|
||||||
|
placeholders,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'FORGOT_PASSWORD',
|
||||||
|
'Forgot Password Reset',
|
||||||
|
'EMAIL',
|
||||||
|
'Reset your ERP password',
|
||||||
|
'<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body style="font-family: Arial, sans-serif; color: #222; line-height: 1.5;">
|
||||||
|
<p>Hello {{user_name}},</p>
|
||||||
|
<p>We received a request to reset your password.</p>
|
||||||
|
<p><a href="{{reset_url}}" style="display: inline-block; padding: 10px 16px; background: #1f4b99; color: #fff; text-decoration: none; border-radius: 4px;">Click here to reset your password</a></p>
|
||||||
|
<p>This link expires in {{expiry_minutes}} minutes.</p>
|
||||||
|
<p style="color: #666; font-size: 12px;">If the button does not work, open this link:<br/>{{reset_url}}</p>
|
||||||
|
<p>If you did not request this, you can ignore this email.</p>
|
||||||
|
</body>
|
||||||
|
</html>',
|
||||||
|
'[
|
||||||
|
{"key": "user_name", "description": "User full name"},
|
||||||
|
{"key": "reset_url", "description": "Password reset frontend URL with token"},
|
||||||
|
{"key": "expiry_minutes", "description": "Reset link expiry in minutes"}
|
||||||
|
]'::jsonb,
|
||||||
|
'Sent when a user requests a password reset via forgot-password',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
ON CONFLICT (code) DO UPDATE SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
channel = EXCLUDED.channel,
|
||||||
|
subject = EXCLUDED.subject,
|
||||||
|
html_body = EXCLUDED.html_body,
|
||||||
|
placeholders = EXCLUDED.placeholders,
|
||||||
|
description = EXCLUDED.description,
|
||||||
|
is_active = true,
|
||||||
|
deleted_at = NULL,
|
||||||
|
updated_at = NOW();
|
||||||
@ -93,3 +93,48 @@ ON CONFLICT (code) DO UPDATE SET
|
|||||||
is_active = true,
|
is_active = true,
|
||||||
deleted_at = NULL,
|
deleted_at = NULL,
|
||||||
updated_at = NOW();
|
updated_at = NOW();
|
||||||
|
|
||||||
|
INSERT INTO notification_templates (
|
||||||
|
code,
|
||||||
|
name,
|
||||||
|
channel,
|
||||||
|
subject,
|
||||||
|
html_body,
|
||||||
|
placeholders,
|
||||||
|
description,
|
||||||
|
is_active
|
||||||
|
)
|
||||||
|
VALUES (
|
||||||
|
'FORGOT_PASSWORD',
|
||||||
|
'Forgot Password Reset',
|
||||||
|
'EMAIL',
|
||||||
|
'Reset your ERP password',
|
||||||
|
'<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body style="font-family: Arial, sans-serif; color: #222; line-height: 1.5;">
|
||||||
|
<p>Hello {{user_name}},</p>
|
||||||
|
<p>We received a request to reset your password.</p>
|
||||||
|
<p><a href="{{reset_url}}" style="display: inline-block; padding: 10px 16px; background: #1f4b99; color: #fff; text-decoration: none; border-radius: 4px;">Click here to reset your password</a></p>
|
||||||
|
<p>This link expires in {{expiry_minutes}} minutes.</p>
|
||||||
|
<p style="color: #666; font-size: 12px;">If the button does not work, open this link:<br/>{{reset_url}}</p>
|
||||||
|
<p>If you did not request this, you can ignore this email.</p>
|
||||||
|
</body>
|
||||||
|
</html>',
|
||||||
|
'[
|
||||||
|
{"key": "user_name", "description": "User full name"},
|
||||||
|
{"key": "reset_url", "description": "Password reset frontend URL with token"},
|
||||||
|
{"key": "expiry_minutes", "description": "Reset link expiry in minutes"}
|
||||||
|
]'::jsonb,
|
||||||
|
'Sent when a user requests a password reset via forgot-password',
|
||||||
|
true
|
||||||
|
)
|
||||||
|
ON CONFLICT (code) DO UPDATE SET
|
||||||
|
name = EXCLUDED.name,
|
||||||
|
channel = EXCLUDED.channel,
|
||||||
|
subject = EXCLUDED.subject,
|
||||||
|
html_body = EXCLUDED.html_body,
|
||||||
|
placeholders = EXCLUDED.placeholders,
|
||||||
|
description = EXCLUDED.description,
|
||||||
|
is_active = true,
|
||||||
|
deleted_at = NULL,
|
||||||
|
updated_at = NOW();
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const auditLog = require('../../utils/auditLog');
|
|||||||
const env = require('../../config/env');
|
const env = require('../../config/env');
|
||||||
const { encrypt, decrypt, blindIndex } = require('../../utils/encryption');
|
const { encrypt, decrypt, blindIndex } = require('../../utils/encryption');
|
||||||
const { collectUserPermissions, extractUserRoles } = require('../../utils/userPermissions');
|
const { collectUserPermissions, extractUserRoles } = require('../../utils/userPermissions');
|
||||||
const { sendMail } = require('../../utils/email');
|
const { sendTemplatedEmail, TEMPLATE_CODES } = require('../notifications/notifications.service');
|
||||||
|
|
||||||
const hashToken = (token) => crypto.createHash('sha256').update(token).digest('hex');
|
const hashToken = (token) => crypto.createHash('sha256').update(token).digest('hex');
|
||||||
|
|
||||||
@ -344,30 +344,16 @@ const forgotPassword = async (email) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const resetUrl = `${env.FRONTEND_URL.replace(/\/$/, '')}/reset-password?token=${rawToken}`;
|
const resetUrl = `${env.FRONTEND_URL.replace(/\/$/, '')}/reset-password?token=${rawToken}`;
|
||||||
const subject = 'Reset your ERP password';
|
|
||||||
const text = [
|
|
||||||
`Hello ${user.full_name},`,
|
|
||||||
'',
|
|
||||||
'We received a request to reset your password.',
|
|
||||||
`Open this link to set a new password (valid for ${env.PASSWORD_RESET_EXPIRY_MINUTES} minutes):`,
|
|
||||||
resetUrl,
|
|
||||||
'',
|
|
||||||
'If you did not request this, you can ignore this email.',
|
|
||||||
].join('\n');
|
|
||||||
|
|
||||||
const html = `
|
await sendTemplatedEmail({
|
||||||
<p>Hello ${user.full_name},</p>
|
templateCode: TEMPLATE_CODES.FORGOT_PASSWORD,
|
||||||
<p>We received a request to reset your password.</p>
|
|
||||||
<p><a href="${resetUrl}">Click here to reset your password</a></p>
|
|
||||||
<p>This link expires in ${env.PASSWORD_RESET_EXPIRY_MINUTES} minutes.</p>
|
|
||||||
<p>If you did not request this, you can ignore this email.</p>
|
|
||||||
`;
|
|
||||||
|
|
||||||
await sendMail({
|
|
||||||
to: user.email,
|
to: user.email,
|
||||||
subject,
|
data: {
|
||||||
text,
|
user_name: user.full_name || 'User',
|
||||||
html,
|
reset_url: resetUrl,
|
||||||
|
expiry_minutes: env.PASSWORD_RESET_EXPIRY_MINUTES,
|
||||||
|
},
|
||||||
|
context: { userId: String(user.id) },
|
||||||
});
|
});
|
||||||
|
|
||||||
return { message: genericMessage };
|
return { message: genericMessage };
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
const TEMPLATE_CODES = {
|
const TEMPLATE_CODES = {
|
||||||
PO_SUBMIT_APPROVAL: 'PO_SUBMIT_APPROVAL',
|
PO_SUBMIT_APPROVAL: 'PO_SUBMIT_APPROVAL',
|
||||||
|
FORGOT_PASSWORD: 'FORGOT_PASSWORD',
|
||||||
};
|
};
|
||||||
|
|
||||||
const CHANNELS = {
|
const CHANNELS = {
|
||||||
|
|||||||
@ -1,13 +1,17 @@
|
|||||||
const Joi = require('joi');
|
const Joi = require('joi');
|
||||||
const { TEMPLATE_CODES } = require('./notifications.constants');
|
const { TEMPLATE_CODES } = require('./notifications.constants');
|
||||||
|
|
||||||
|
/** Templates that can be triggered from the FE button API */
|
||||||
|
const TRIGGERABLE_TEMPLATE_CODES = [TEMPLATE_CODES.PO_SUBMIT_APPROVAL];
|
||||||
|
|
||||||
const triggerNotificationSchema = Joi.object({
|
const triggerNotificationSchema = Joi.object({
|
||||||
template_code: Joi.string()
|
template_code: Joi.string()
|
||||||
.valid(...Object.values(TEMPLATE_CODES))
|
.valid(...TRIGGERABLE_TEMPLATE_CODES)
|
||||||
.required(),
|
.required(),
|
||||||
po_id: Joi.alternatives().try(Joi.number().integer().positive(), Joi.string().pattern(/^\d+$/)).required(),
|
po_id: Joi.alternatives().try(Joi.number().integer().positive(), Joi.string().pattern(/^\d+$/)).required(),
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
triggerNotificationSchema,
|
triggerNotificationSchema,
|
||||||
|
TRIGGERABLE_TEMPLATE_CODES,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user