@growy/strapi-plugin-encrypted-field 2.2.1 → 2.3.0
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/admin/src/components/Input.jsx +37 -21
- package/admin/src/index.js +8 -17
- package/admin/src/translations/en.json +7 -2
- package/admin/src/translations/es.json +7 -2
- package/package.json +5 -3
- package/server/bootstrap.js +55 -93
- package/server/index.js +4 -0
- package/server/middlewares/decrypt.js +22 -14
- package/server/register.js +6 -2
- package/server/utils/crypto.js +40 -31
|
@@ -34,29 +34,45 @@ const Input = (props) => {
|
|
|
34
34
|
};
|
|
35
35
|
|
|
36
36
|
const handleCopy = async () => {
|
|
37
|
-
if (value)
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
37
|
+
if (!value) return;
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
await navigator.clipboard.writeText(value);
|
|
41
|
+
toggleNotification({
|
|
42
|
+
type: 'success',
|
|
43
|
+
message: formatMessage({
|
|
44
|
+
id: 'encrypted-field.notification.copied',
|
|
45
|
+
defaultMessage: 'Copiado al portapapeles',
|
|
46
|
+
}),
|
|
47
|
+
});
|
|
48
|
+
} catch (err) {
|
|
49
|
+
toggleNotification({
|
|
50
|
+
type: 'danger',
|
|
51
|
+
message: formatMessage({
|
|
52
|
+
id: 'encrypted-field.notification.copyError',
|
|
53
|
+
defaultMessage: 'Error al copiar',
|
|
54
|
+
}),
|
|
55
|
+
});
|
|
50
56
|
}
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
const toggleVisibility = () => {
|
|
54
|
-
setIsVisible(!
|
|
60
|
+
setIsVisible((prev) => !prev);
|
|
55
61
|
};
|
|
56
62
|
|
|
57
63
|
const fieldName = name.includes('.') ? name.split('.').pop() : name;
|
|
58
64
|
const label = intlLabel?.id ? formatMessage(intlLabel) : (intlLabel || fieldName);
|
|
59
65
|
|
|
66
|
+
const visibilityLabel = formatMessage({
|
|
67
|
+
id: isVisible ? 'encrypted-field.action.hide' : 'encrypted-field.action.show',
|
|
68
|
+
defaultMessage: isVisible ? 'Ocultar' : 'Mostrar',
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const copyLabel = formatMessage({
|
|
72
|
+
id: 'encrypted-field.action.copy',
|
|
73
|
+
defaultMessage: 'Copiar',
|
|
74
|
+
});
|
|
75
|
+
|
|
60
76
|
return (
|
|
61
77
|
<Field.Root
|
|
62
78
|
name={name}
|
|
@@ -77,17 +93,17 @@ const Input = (props) => {
|
|
|
77
93
|
disabled={disabled}
|
|
78
94
|
style={{ paddingRight: '80px' }}
|
|
79
95
|
/>
|
|
80
|
-
<div style={{
|
|
81
|
-
position: 'absolute',
|
|
82
|
-
right: '8px',
|
|
83
|
-
top: '50%',
|
|
96
|
+
<div style={{
|
|
97
|
+
position: 'absolute',
|
|
98
|
+
right: '8px',
|
|
99
|
+
top: '50%',
|
|
84
100
|
transform: 'translateY(-50%)',
|
|
85
101
|
display: 'flex',
|
|
86
|
-
gap: '4px'
|
|
102
|
+
gap: '4px',
|
|
87
103
|
}}>
|
|
88
104
|
<IconButton
|
|
89
105
|
onClick={toggleVisibility}
|
|
90
|
-
label={
|
|
106
|
+
label={visibilityLabel}
|
|
91
107
|
disabled={disabled}
|
|
92
108
|
variant="ghost"
|
|
93
109
|
>
|
|
@@ -95,7 +111,7 @@ const Input = (props) => {
|
|
|
95
111
|
</IconButton>
|
|
96
112
|
<IconButton
|
|
97
113
|
onClick={handleCopy}
|
|
98
|
-
label=
|
|
114
|
+
label={copyLabel}
|
|
99
115
|
disabled={disabled || !value}
|
|
100
116
|
variant="ghost"
|
|
101
117
|
>
|
package/admin/src/index.js
CHANGED
|
@@ -103,24 +103,15 @@ export default {
|
|
|
103
103
|
},
|
|
104
104
|
|
|
105
105
|
async registerTrads({ locales }) {
|
|
106
|
-
|
|
107
|
-
locales.map((locale) => {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
})
|
|
115
|
-
.catch(() => {
|
|
116
|
-
return {
|
|
117
|
-
data: {},
|
|
118
|
-
locale,
|
|
119
|
-
};
|
|
120
|
-
});
|
|
106
|
+
return Promise.all(
|
|
107
|
+
locales.map(async (locale) => {
|
|
108
|
+
try {
|
|
109
|
+
const { default: data } = await import(`./translations/${locale}.json`);
|
|
110
|
+
return { data, locale };
|
|
111
|
+
} catch {
|
|
112
|
+
return { data: {}, locale };
|
|
113
|
+
}
|
|
121
114
|
})
|
|
122
115
|
);
|
|
123
|
-
|
|
124
|
-
return Promise.resolve(importedTrads);
|
|
125
116
|
},
|
|
126
117
|
};
|
|
@@ -13,5 +13,10 @@
|
|
|
13
13
|
"encrypted-field.options.minLength.description": "Minimum number of characters required",
|
|
14
14
|
"encrypted-field.options.advanced.regex": "Validation",
|
|
15
15
|
"encrypted-field.options.regex.label": "RegEx pattern",
|
|
16
|
-
"encrypted-field.options.regex.description": "Validation pattern before encryption"
|
|
17
|
-
|
|
16
|
+
"encrypted-field.options.regex.description": "Validation pattern before encryption",
|
|
17
|
+
"encrypted-field.notification.copied": "Copied to clipboard",
|
|
18
|
+
"encrypted-field.notification.copyError": "Error copying to clipboard",
|
|
19
|
+
"encrypted-field.action.show": "Show",
|
|
20
|
+
"encrypted-field.action.hide": "Hide",
|
|
21
|
+
"encrypted-field.action.copy": "Copy"
|
|
22
|
+
}
|
|
@@ -13,5 +13,10 @@
|
|
|
13
13
|
"encrypted-field.options.minLength.description": "Número mínimo de caracteres requeridos",
|
|
14
14
|
"encrypted-field.options.advanced.regex": "Validación",
|
|
15
15
|
"encrypted-field.options.regex.label": "RegEx pattern",
|
|
16
|
-
"encrypted-field.options.regex.description": "Patrón de validación antes de cifrar"
|
|
17
|
-
|
|
16
|
+
"encrypted-field.options.regex.description": "Patrón de validación antes de cifrar",
|
|
17
|
+
"encrypted-field.notification.copied": "Copiado al portapapeles",
|
|
18
|
+
"encrypted-field.notification.copyError": "Error al copiar al portapapeles",
|
|
19
|
+
"encrypted-field.action.show": "Mostrar",
|
|
20
|
+
"encrypted-field.action.hide": "Ocultar",
|
|
21
|
+
"encrypted-field.action.copy": "Copiar"
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@growy/strapi-plugin-encrypted-field",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Campo personalizado de texto cifrado para Strapi",
|
|
5
5
|
"strapi": {
|
|
6
6
|
"name": "encrypted-field",
|
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
},
|
|
11
11
|
"dependencies": {},
|
|
12
12
|
"peerDependencies": {
|
|
13
|
-
"@strapi/strapi": "^5.0.0"
|
|
13
|
+
"@strapi/strapi": "^5.0.0",
|
|
14
|
+
"@strapi/design-system": "^2.0.0",
|
|
15
|
+
"react": "^18.0.0"
|
|
14
16
|
},
|
|
15
17
|
"main": "server/index.js",
|
|
16
18
|
"exports": {
|
|
@@ -53,4 +55,4 @@
|
|
|
53
55
|
"publishConfig": {
|
|
54
56
|
"access": "public"
|
|
55
57
|
}
|
|
56
|
-
}
|
|
58
|
+
}
|
package/server/bootstrap.js
CHANGED
|
@@ -1,117 +1,79 @@
|
|
|
1
1
|
const { encrypt, decrypt, validateValue, isEncryptedField } = require('./utils/crypto');
|
|
2
2
|
|
|
3
|
+
function processEncryption(event, strapi) {
|
|
4
|
+
if (!event.model?.uid) return;
|
|
5
|
+
|
|
6
|
+
const { data } = event.params;
|
|
7
|
+
const currentModel = strapi.getModel(event.model.uid);
|
|
8
|
+
|
|
9
|
+
if (!currentModel?.attributes) return;
|
|
10
|
+
|
|
11
|
+
for (const [key, attribute] of Object.entries(currentModel.attributes)) {
|
|
12
|
+
if (!isEncryptedField(attribute)) continue;
|
|
13
|
+
if (!Object.prototype.hasOwnProperty.call(data, key)) continue;
|
|
14
|
+
|
|
15
|
+
const value = data[key];
|
|
16
|
+
if (value === null || value === undefined || value === '') continue;
|
|
17
|
+
|
|
18
|
+
const validation = validateValue(value, attribute);
|
|
19
|
+
if (!validation.valid) {
|
|
20
|
+
throw new Error(`Validación fallida para el campo "${key}": ${validation.error}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
data[key] = encrypt(value, strapi);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function processDecryption(result, event, strapi) {
|
|
28
|
+
if (!result) return;
|
|
29
|
+
if (!event.model?.uid) return;
|
|
30
|
+
|
|
31
|
+
const currentModel = strapi.getModel(event.model.uid);
|
|
32
|
+
if (!currentModel?.attributes) return;
|
|
33
|
+
|
|
34
|
+
for (const [key, attribute] of Object.entries(currentModel.attributes)) {
|
|
35
|
+
if (!isEncryptedField(attribute)) continue;
|
|
36
|
+
if (!Object.prototype.hasOwnProperty.call(result, key)) continue;
|
|
37
|
+
|
|
38
|
+
const value = result[key];
|
|
39
|
+
if (typeof value === 'string' && value) {
|
|
40
|
+
result[key] = decrypt(value, strapi);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
3
45
|
module.exports = ({ strapi }) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
46
|
+
const allModels = [
|
|
47
|
+
...Object.values(strapi.contentTypes),
|
|
48
|
+
...Object.values(strapi.components),
|
|
49
|
+
];
|
|
50
|
+
|
|
10
51
|
allModels.forEach((model) => {
|
|
11
52
|
const attributes = model.attributes || {};
|
|
12
|
-
|
|
13
|
-
const encryptedFields = Object.entries(attributes)
|
|
14
|
-
.filter(([key, attr]) => isEncryptedField(attr))
|
|
15
|
-
.map(([key]) => key);
|
|
16
|
-
|
|
17
|
-
if (encryptedFields.length === 0) return;
|
|
53
|
+
const hasEncryptedFields = Object.values(attributes).some(isEncryptedField);
|
|
18
54
|
|
|
19
|
-
|
|
55
|
+
if (!hasEncryptedFields) return;
|
|
20
56
|
|
|
21
|
-
|
|
22
57
|
strapi.db.lifecycles.subscribe({
|
|
23
|
-
models: [uid],
|
|
24
|
-
|
|
58
|
+
models: [model.uid],
|
|
59
|
+
|
|
25
60
|
async beforeCreate(event) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (!event.model?.uid) return;
|
|
29
|
-
|
|
30
|
-
const currentModel = strapi.getModel(event.model.uid);
|
|
31
|
-
|
|
32
|
-
if (!currentModel?.attributes) return;
|
|
33
|
-
|
|
34
|
-
for (const [key, attribute] of Object.entries(currentModel.attributes)) {
|
|
35
|
-
if (!isEncryptedField(attribute)) continue;
|
|
36
|
-
|
|
37
|
-
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
38
|
-
const value = data[key];
|
|
39
|
-
|
|
40
|
-
if (value === null || value === undefined || value === '') continue;
|
|
41
|
-
|
|
42
|
-
const validation = validateValue(value, attribute);
|
|
43
|
-
if (!validation.valid) {
|
|
44
|
-
throw new Error(`Validación fallida para el campo "${key}": ${validation.error}`);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
data[key] = encrypt(value, strapi);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
61
|
+
processEncryption(event, strapi);
|
|
50
62
|
},
|
|
51
63
|
|
|
52
64
|
async beforeUpdate(event) {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const { data } = event.params;
|
|
56
|
-
const currentModel = strapi.getModel(event.model.uid);
|
|
57
|
-
|
|
58
|
-
if (!currentModel?.attributes) return;
|
|
59
|
-
|
|
60
|
-
for (const [key, attribute] of Object.entries(currentModel.attributes)) {
|
|
61
|
-
if (!isEncryptedField(attribute)) continue;
|
|
62
|
-
if (Object.prototype.hasOwnProperty.call(data, key)) {
|
|
63
|
-
const value = data[key];
|
|
64
|
-
if (value === null || value === undefined || value === '') continue;
|
|
65
|
-
|
|
66
|
-
const validation = validateValue(value, attribute);
|
|
67
|
-
if (!validation.valid) {
|
|
68
|
-
throw new Error(`Validación fallida para el campo "${key}": ${validation.error}`);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
data[key] = encrypt(value, strapi);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
65
|
+
processEncryption(event, strapi);
|
|
74
66
|
},
|
|
75
67
|
|
|
76
68
|
async afterFindOne(event) {
|
|
77
|
-
|
|
78
|
-
if (!result) return;
|
|
79
|
-
if (!event.model?.uid) return;
|
|
80
|
-
|
|
81
|
-
const currentModel = strapi.getModel(event.model.uid);
|
|
82
|
-
|
|
83
|
-
if (!currentModel?.attributes) return;
|
|
84
|
-
|
|
85
|
-
for (const [key, attribute] of Object.entries(currentModel.attributes)) {
|
|
86
|
-
if (!isEncryptedField(attribute)) continue;
|
|
87
|
-
if (Object.prototype.hasOwnProperty.call(result, key)) {
|
|
88
|
-
const value = result[key];
|
|
89
|
-
if (typeof value === 'string' && value) {
|
|
90
|
-
result[key] = decrypt(value, strapi);
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
69
|
+
processDecryption(event.result, event, strapi);
|
|
94
70
|
},
|
|
95
71
|
|
|
96
72
|
async afterFindMany(event) {
|
|
97
73
|
const { result } = event;
|
|
98
74
|
if (!result || !Array.isArray(result)) return;
|
|
99
|
-
if (!event.model?.uid) return;
|
|
100
|
-
|
|
101
|
-
const currentModel = strapi.getModel(event.model.uid);
|
|
102
|
-
|
|
103
|
-
if (!currentModel?.attributes) return;
|
|
104
|
-
|
|
105
75
|
for (const item of result) {
|
|
106
|
-
|
|
107
|
-
if (!isEncryptedField(attribute)) continue;
|
|
108
|
-
if (Object.prototype.hasOwnProperty.call(item, key)) {
|
|
109
|
-
const value = item[key];
|
|
110
|
-
if (typeof value === 'string' && value) {
|
|
111
|
-
item[key] = decrypt(value, strapi);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
76
|
+
processDecryption(item, event, strapi);
|
|
115
77
|
}
|
|
116
78
|
},
|
|
117
79
|
});
|
package/server/index.js
CHANGED
|
@@ -1,38 +1,49 @@
|
|
|
1
1
|
const { decrypt, isEncryptedField } = require('../utils/crypto');
|
|
2
2
|
|
|
3
|
+
function resolveModelUidFromPath(ctx, strapi) {
|
|
4
|
+
const path = ctx.request?.path || '';
|
|
5
|
+
const match = path.match(/^\/api\/([^/]+)/);
|
|
6
|
+
if (!match) return null;
|
|
7
|
+
|
|
8
|
+
const slug = match[1];
|
|
9
|
+
const contentTypes = Object.values(strapi.contentTypes);
|
|
10
|
+
|
|
11
|
+
const ct = contentTypes.find((ct) => {
|
|
12
|
+
const info = ct.info || {};
|
|
13
|
+
return info.pluralName === slug || info.singularName === slug;
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
return ct?.uid || null;
|
|
17
|
+
}
|
|
18
|
+
|
|
3
19
|
module.exports = (config, { strapi }) => {
|
|
4
20
|
return async (ctx, next) => {
|
|
5
21
|
await next();
|
|
6
22
|
|
|
7
23
|
if (!ctx.body) return;
|
|
8
24
|
|
|
9
|
-
|
|
25
|
+
const rootModelUid = resolveModelUidFromPath(ctx, strapi);
|
|
26
|
+
|
|
10
27
|
const decryptRecursive = (obj, modelUid = null) => {
|
|
11
28
|
if (!obj || typeof obj !== 'object') return;
|
|
12
29
|
|
|
13
|
-
|
|
14
30
|
if (Array.isArray(obj)) {
|
|
15
|
-
obj.forEach(item => decryptRecursive(item, modelUid));
|
|
31
|
+
obj.forEach((item) => decryptRecursive(item, modelUid));
|
|
16
32
|
return;
|
|
17
33
|
}
|
|
18
34
|
|
|
19
|
-
|
|
20
35
|
let currentModelUid = modelUid;
|
|
21
36
|
if (obj.__component) {
|
|
22
37
|
currentModelUid = obj.__component;
|
|
23
38
|
}
|
|
24
39
|
|
|
25
|
-
|
|
26
40
|
let model = null;
|
|
27
41
|
if (currentModelUid) {
|
|
28
42
|
try {
|
|
29
43
|
model = strapi.getModel(currentModelUid) || strapi.components[currentModelUid];
|
|
30
|
-
} catch (e) {
|
|
31
|
-
|
|
32
|
-
}
|
|
44
|
+
} catch (e) { }
|
|
33
45
|
}
|
|
34
46
|
|
|
35
|
-
|
|
36
47
|
if (model?.attributes) {
|
|
37
48
|
for (const [key, attribute] of Object.entries(model.attributes)) {
|
|
38
49
|
if (isEncryptedField(attribute) && obj[key] && typeof obj[key] === 'string') {
|
|
@@ -45,7 +56,6 @@ module.exports = (config, { strapi }) => {
|
|
|
45
56
|
}
|
|
46
57
|
}
|
|
47
58
|
|
|
48
|
-
|
|
49
59
|
for (const value of Object.values(obj)) {
|
|
50
60
|
if (value && typeof value === 'object') {
|
|
51
61
|
decryptRecursive(value, currentModelUid);
|
|
@@ -53,12 +63,10 @@ module.exports = (config, { strapi }) => {
|
|
|
53
63
|
}
|
|
54
64
|
};
|
|
55
65
|
|
|
56
|
-
|
|
57
66
|
if (ctx.body.data) {
|
|
58
|
-
decryptRecursive(ctx.body.data);
|
|
67
|
+
decryptRecursive(ctx.body.data, rootModelUid);
|
|
59
68
|
} else {
|
|
60
|
-
|
|
61
|
-
decryptRecursive(ctx.body);
|
|
69
|
+
decryptRecursive(ctx.body, rootModelUid);
|
|
62
70
|
}
|
|
63
71
|
};
|
|
64
72
|
};
|
package/server/register.js
CHANGED
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
module.exports = ({ strapi }) => {
|
|
2
2
|
const decryptMiddleware = require('./middlewares/decrypt');
|
|
3
|
-
|
|
3
|
+
|
|
4
4
|
strapi.server.use(decryptMiddleware({}, { strapi }));
|
|
5
|
-
|
|
5
|
+
|
|
6
6
|
strapi.customFields.register({
|
|
7
7
|
name: 'encrypted-text',
|
|
8
8
|
plugin: 'encrypted-field',
|
|
9
9
|
type: 'string',
|
|
10
|
+
inputSize: {
|
|
11
|
+
default: 6,
|
|
12
|
+
isResizable: true,
|
|
13
|
+
},
|
|
10
14
|
});
|
|
11
15
|
};
|
package/server/utils/crypto.js
CHANGED
|
@@ -5,9 +5,12 @@ const IV_LENGTH = 12;
|
|
|
5
5
|
const AUTH_TAG_LENGTH = 16;
|
|
6
6
|
const KEY_LENGTH = 32;
|
|
7
7
|
|
|
8
|
+
let _cachedKey = null;
|
|
9
|
+
let _cachedKeySource = null;
|
|
10
|
+
|
|
8
11
|
function getEncryptionKey(strapi) {
|
|
9
12
|
const key = process.env.ENCRYPTION_KEY || strapi?.config?.get('plugin.encrypted-field.encryptionKey');
|
|
10
|
-
|
|
13
|
+
|
|
11
14
|
if (!key) {
|
|
12
15
|
const errorMsg = '⚠️ ENCRYPTION_KEY no configurada. Debe establecer una clave de 64 caracteres hexadecimales en las variables de entorno o configuración de Strapi.';
|
|
13
16
|
if (strapi?.log?.error) {
|
|
@@ -15,33 +18,39 @@ function getEncryptionKey(strapi) {
|
|
|
15
18
|
}
|
|
16
19
|
throw new Error(errorMsg);
|
|
17
20
|
}
|
|
18
|
-
|
|
21
|
+
|
|
22
|
+
if (_cachedKey && _cachedKeySource === key) {
|
|
23
|
+
return _cachedKey;
|
|
24
|
+
}
|
|
25
|
+
|
|
19
26
|
if (typeof key !== 'string' || key.length !== 64) {
|
|
20
27
|
throw new Error(`ENCRYPTION_KEY debe tener exactamente 64 caracteres hexadecimales (32 bytes). Actual: ${key?.length || 0}`);
|
|
21
28
|
}
|
|
22
|
-
|
|
29
|
+
|
|
23
30
|
if (!/^[0-9a-fA-F]{64}$/.test(key)) {
|
|
24
31
|
throw new Error('ENCRYPTION_KEY debe contener solo caracteres hexadecimales (0-9, a-f, A-F)');
|
|
25
32
|
}
|
|
26
|
-
|
|
27
|
-
|
|
33
|
+
|
|
34
|
+
_cachedKey = Buffer.from(key, 'hex');
|
|
35
|
+
_cachedKeySource = key;
|
|
36
|
+
return _cachedKey;
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
function encrypt(text, strapi) {
|
|
31
40
|
if (typeof text !== 'string') return text;
|
|
32
|
-
|
|
41
|
+
|
|
33
42
|
if (text === '') return text;
|
|
34
43
|
|
|
35
44
|
try {
|
|
36
45
|
const ENCRYPTION_KEY = getEncryptionKey(strapi);
|
|
37
46
|
const iv = crypto.randomBytes(IV_LENGTH);
|
|
38
47
|
const cipher = crypto.createCipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
|
|
39
|
-
|
|
48
|
+
|
|
40
49
|
let encrypted = cipher.update(text, 'utf8', 'hex');
|
|
41
50
|
encrypted += cipher.final('hex');
|
|
42
|
-
|
|
51
|
+
|
|
43
52
|
const authTag = cipher.getAuthTag();
|
|
44
|
-
|
|
53
|
+
|
|
45
54
|
return `${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}`;
|
|
46
55
|
} catch (error) {
|
|
47
56
|
if (strapi?.log?.error) {
|
|
@@ -53,29 +62,29 @@ function encrypt(text, strapi) {
|
|
|
53
62
|
|
|
54
63
|
function decrypt(encryptedText, strapi) {
|
|
55
64
|
if (!encryptedText || typeof encryptedText !== 'string') return encryptedText;
|
|
56
|
-
|
|
65
|
+
|
|
57
66
|
const parts = encryptedText.split(':');
|
|
58
67
|
if (parts.length !== 3) {
|
|
59
68
|
return encryptedText;
|
|
60
69
|
}
|
|
61
|
-
|
|
70
|
+
|
|
62
71
|
try {
|
|
63
72
|
const [ivHex, authTagHex, encrypted] = parts;
|
|
64
|
-
|
|
73
|
+
|
|
65
74
|
if (ivHex.length !== IV_LENGTH * 2 || authTagHex.length !== AUTH_TAG_LENGTH * 2) {
|
|
66
75
|
return encryptedText;
|
|
67
76
|
}
|
|
68
|
-
|
|
77
|
+
|
|
69
78
|
const ENCRYPTION_KEY = getEncryptionKey(strapi);
|
|
70
79
|
const iv = Buffer.from(ivHex, 'hex');
|
|
71
80
|
const authTag = Buffer.from(authTagHex, 'hex');
|
|
72
|
-
|
|
81
|
+
|
|
73
82
|
const decipher = crypto.createDecipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
|
|
74
83
|
decipher.setAuthTag(authTag);
|
|
75
|
-
|
|
84
|
+
|
|
76
85
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
77
86
|
decrypted += decipher.final('utf8');
|
|
78
|
-
|
|
87
|
+
|
|
79
88
|
return decrypted;
|
|
80
89
|
} catch (error) {
|
|
81
90
|
if (strapi?.log?.debug) {
|
|
@@ -91,9 +100,9 @@ function validateValue(value, attribute) {
|
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
if (typeof value !== 'string') {
|
|
94
|
-
return {
|
|
95
|
-
valid: false,
|
|
96
|
-
error: 'El valor debe ser una cadena de texto'
|
|
103
|
+
return {
|
|
104
|
+
valid: false,
|
|
105
|
+
error: 'El valor debe ser una cadena de texto'
|
|
97
106
|
};
|
|
98
107
|
}
|
|
99
108
|
|
|
@@ -101,30 +110,30 @@ function validateValue(value, attribute) {
|
|
|
101
110
|
try {
|
|
102
111
|
const regex = new RegExp(attribute.regex);
|
|
103
112
|
if (!regex.test(value)) {
|
|
104
|
-
return {
|
|
105
|
-
valid: false,
|
|
106
|
-
error: `El valor no cumple con el patrón de validación: ${attribute.regex}`
|
|
113
|
+
return {
|
|
114
|
+
valid: false,
|
|
115
|
+
error: `El valor no cumple con el patrón de validación: ${attribute.regex}`
|
|
107
116
|
};
|
|
108
117
|
}
|
|
109
118
|
} catch (error) {
|
|
110
|
-
return {
|
|
111
|
-
valid: false,
|
|
112
|
-
error: `Patrón regex inválido: ${error.message}`
|
|
119
|
+
return {
|
|
120
|
+
valid: false,
|
|
121
|
+
error: `Patrón regex inválido: ${error.message}`
|
|
113
122
|
};
|
|
114
123
|
}
|
|
115
124
|
}
|
|
116
125
|
|
|
117
126
|
if (attribute.maxLength && value.length > attribute.maxLength) {
|
|
118
|
-
return {
|
|
119
|
-
valid: false,
|
|
120
|
-
error: `El valor excede la longitud máxima de ${attribute.maxLength} caracteres`
|
|
127
|
+
return {
|
|
128
|
+
valid: false,
|
|
129
|
+
error: `El valor excede la longitud máxima de ${attribute.maxLength} caracteres`
|
|
121
130
|
};
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
if (attribute.minLength && value.length < attribute.minLength) {
|
|
125
|
-
return {
|
|
126
|
-
valid: false,
|
|
127
|
-
error: `El valor debe tener al menos ${attribute.minLength} caracteres`
|
|
134
|
+
return {
|
|
135
|
+
valid: false,
|
|
136
|
+
error: `El valor debe tener al menos ${attribute.minLength} caracteres`
|
|
128
137
|
};
|
|
129
138
|
}
|
|
130
139
|
|