@websolutespa/payload-plugin-bowl
Version:
Bowl PayloadCms plugin of the BOM Repository
301 lines (300 loc) • 10.7 kB
JavaScript
import { deepMerge, eachDataField, HttpStatus, withCollectionHook } from '@websolutespa/payload-utils';
import { ResponseError, ResponseSuccess } from '@websolutespa/payload-utils/server';
import { addDataAndFileToRequest, AuthenticationError } from 'payload';
import { toField } from '../../mapper';
import { options } from '../../options';
import { hasRole, isAdmin, isAdminOrSelf } from '../access';
import { sendEmail } from '../api/email.service';
import { decrypt, encrypt } from '../encryption';
import { translateCollection } from '../translations';
import { mergeFields } from '../utils';
export const EndUserDefault = {
access: {
create: ()=>false,
read: isAdminOrSelf,
update: isAdminOrSelf,
delete: ()=>false,
unlock: isAdmin
},
admin: {
useAsTitle: 'email',
group: options.group.users,
defaultColumns: [
'email'
]
},
auth: {
useAPIKey: false,
lockTime: 10 * 60 * 1000,
maxLoginAttempts: 5,
tokenExpiration: 2 * 60 * 60,
verify: false
}
};
/**
* End User before login hook.
*
* @param req - Full express request.
* @param user - User being logged in.
* @returns The logged in user.
* @throws {AuthenticationError} If the endUser does not have the role 'user' associated (this ensure endUser cannot authenticate before optin).
*/ const beforeLoginHook = async ({ req, user })=>{
if (!hasRole(user, options.roles.User)) {
throw new AuthenticationError();
}
return user;
};
export const endUserForgotPost = (slug)=>({
path: '/forgot',
method: 'post',
handler: async (req)=>{
try {
const { payload, user } = req;
if (!hasRole(user, options.roles.Guest)) {
throw new AuthenticationError();
}
await addDataAndFileToRequest(req);
const { email } = req.data;
if (!email) {
throw {
status: 400,
message: 'Bad Request: email is missing'
};
}
const token = await payload.forgotPassword({
collection: slug,
data: {
email
},
disableEmail: true
});
// console.log('token', token);
if (!token) {
// !!! failing silently, we don't want to let know if email exhist.
return ResponseSuccess({
code: 'SENT'
});
}
const actionId = encrypt(token);
const actionSlug = slug;
await sendEmail(req, 'forgot', (options)=>{
const html = typeof options.html === 'string' ? options.html : '';
options.html = html.replace(/(\{(actionId|actionSlug)\})/gm, (m, g1, g2)=>g2 === 'actionId' ? actionId : actionSlug);
return options;
});
return ResponseSuccess({
code: 'SENT',
actionId
});
} catch (error) {
console.error('withEndUser.endUserForgotPost.error', error);
return ResponseError(error);
}
}
});
export const endUserResetPost = (slug)=>({
path: '/reset',
method: 'post',
handler: async (req)=>{
try {
const { payload, user } = req;
if (!hasRole(user, options.roles.Guest)) {
throw new AuthenticationError();
}
await addDataAndFileToRequest(req);
const { actionId } = req.data;
if (!actionId) {
throw {
status: 400,
message: 'Bad Request: actionId is missing'
};
}
const { password } = req.data;
if (!password) {
throw {
status: 400,
message: 'Bad Request: password is missing'
};
}
const token = decrypt(actionId);
const result = await payload.resetPassword({
collection: slug,
data: {
password,
token
},
overrideAccess: true
});
return ResponseSuccess(result);
} catch (error) {
console.error('withEndUser.endUserResetPost.error', error);
return ResponseError(error);
}
}
});
export const endUserPasswordPost = (slug)=>({
path: '/password',
method: 'post',
handler: async (req)=>{
try {
const { payload, user } = req;
if (!hasRole(user, options.roles.User)) {
throw new AuthenticationError();
}
await addDataAndFileToRequest(req);
const { oldPassword } = req.data;
if (!oldPassword) {
throw {
status: 400,
message: 'Bad Request: oldPassword is missing'
};
}
const { newPassword } = req.data;
if (!newPassword) {
throw {
status: 400,
message: 'Bad Request: newPassword is missing'
};
}
if (!user.email) {
throw {
status: HttpStatus.UNPROCESSABLE_ENTITY,
message: 'Unprocessable Entity: email is missing'
};
}
const oldPasswordResult = await payload.login({
collection: slug,
data: {
email: user.email,
password: oldPassword
}
});
if (!oldPasswordResult.user || !oldPasswordResult.token) {
throw {
status: 401,
message: 'Unauthorized'
};
}
const token = await payload.forgotPassword({
collection: slug,
data: {
email: oldPasswordResult.user.email
},
disableEmail: true
});
if (!token) {
throw {
status: 500,
message: 'Cannot create reset token'
};
}
const result = await payload.resetPassword({
collection: slug,
data: {
password: newPassword,
token
},
overrideAccess: true
});
return ResponseSuccess(result);
} catch (error) {
console.error('withEndUser.endUserForgotPost.error', error);
return ResponseError(error);
}
}
});
export const endUserExistPost = (slug)=>({
path: '/exist',
method: 'post',
handler: async (req)=>{
try {
const { payload } = req;
await addDataAndFileToRequest(req);
const { email } = req.data;
if (!email) {
throw {
status: 400,
message: 'Bad Request: email is missing'
};
}
const existingEndUsers = await payload.find({
collection: slug,
where: {
email: {
equals: email.toLowerCase()
}
},
overrideAccess: true
});
const existingEndUser = existingEndUsers.totalDocs > 0 ? existingEndUsers.docs[0] : undefined;
const exist = existingEndUser && hasRole(existingEndUser, options.roles.User);
return ResponseSuccess({
exist
});
} catch (error) {
console.error('withEndUser.endUserExistPost.error', error);
return ResponseError(error);
}
}
});
export const withEndUser = (config)=>{
const endUserConfig = deepMerge(EndUserDefault, config);
const endUserFields = endUserConfig.fields;
/**
* ATTENTION !
* Automatically setting defaultValue for DataField
*/ eachDataField(endUserFields, (field)=>{
if (field.type !== 'group' && field.required) {
switch(field.type){
case 'number':
field.defaultValue = 0;
break;
default:
field.defaultValue = '-';
}
}
});
const defaultFields = [
toField({
type: 'withRoles',
roles: options.rolesEndUser,
defaultValue: options.roles.Guest
}),
{
name: 'consentPreferences',
type: 'array',
label: 'Consent Preferences',
fields: [
{
name: 'consentPreference',
label: 'Consent Preference',
type: 'relationship',
relationTo: options.slug.consentPreference
},
{
name: 'date',
type: 'date',
label: 'Date'
}
]
},
{
type: 'checkbox',
name: 'emailVerified'
}
];
endUserConfig.fields = mergeFields(defaultFields, endUserConfig.fields, true);
// !!! todo check if
if (!endUserConfig.endpoints) {
endUserConfig.endpoints = [
endUserForgotPost(endUserConfig.slug),
endUserResetPost(endUserConfig.slug),
endUserPasswordPost(endUserConfig.slug),
endUserExistPost(endUserConfig.slug)
];
}
withCollectionHook(endUserConfig, 'beforeLogin', beforeLoginHook);
translateCollection(endUserConfig);
return endUserConfig;
};
//# sourceMappingURL=withEndUser.js.map