update dependencies and devDependencies, address lint issues
[squeep-indie-auther] / src / logger / data-sanitizers.js
1 'use strict';
2
3 /**
4 * Scrub credential from POST login body data.
5 * @param {object} data data
6 * @param {boolean} sanitize do sanitize
7 * @returns {boolean} did/would sanitize
8 */
9 function sanitizePostCredential(data, sanitize = true) {
10 let unclean = false;
11
12 [
13 'credential',
14 'credential-old',
15 'credential-new',
16 'credential-new-2',
17 ].forEach((k) => {
18 const credentialLength = data?.ctx?.parsedBody?.[k]?.length; // eslint-disable-line security/detect-object-injection
19 const kUnclean = !!credentialLength;
20 unclean |= kUnclean;
21 if (kUnclean && sanitize) {
22 data.ctx.parsedBody[k] = '*'.repeat(credentialLength); // eslint-disable-line security/detect-object-injection
23 }
24 });
25
26 return unclean;
27 }
28
29
30 /**
31 * Scrub sensitive data from context.
32 * @param {object} data data
33 * @param {boolean} sanitize do sanitize
34 * @returns {boolean} did/would sanitize
35 */
36 function sanitizeContext(data, sanitize = true) {
37 let unclean = false;
38
39 // hide keys
40 [
41 'otpKey',
42 'otpConfirmKey',
43 ].forEach((k) => {
44 const secretLength = data?.ctx?.[k]?.length; // eslint-disable-line security/detect-object-injection
45 const kUnclean = !! secretLength;
46 unclean |= kUnclean;
47 if (kUnclean && sanitize) {
48 data.ctx[k] = '*'.repeat(secretLength); // eslint-disable-line security/detect-object-injection
49 }
50 });
51
52 // shorten mystery boxes
53 [
54 'otpConfirmBox',
55 'otpState',
56 ].forEach((k) => {
57 const mysteryLength = data?.ctx?.[k]?.length; // eslint-disable-line security/detect-object-injection
58 const mUnclean = !! mysteryLength;
59 unclean |= mUnclean;
60 if (mUnclean && sanitize) {
61 data.ctx[k] = `[scrubbed ${mysteryLength} bytes]`; // eslint-disable-line security/detect-object-injection
62 }
63 });
64
65 const cookieLength = data?.ctx?.cookie?.squeepSession?.length;
66 if (cookieLength) {
67 unclean |= true;
68 if (sanitize) {
69 data.ctx.cookie.squeepSession = `[scrubbed ${cookieLength} bytes]`;
70 }
71 }
72
73 return !! unclean;
74 }
75
76
77 /**
78 * Reduce logged data about scopes from profilesScopes.
79 * For all referenced scopes, only include profiles list.
80 * Remove scopes without profile references from scopeIndex.
81 * @param {object} data data
82 * @param {boolean} sanitize do sanitize
83 * @returns {boolean} did/would sanitize
84 */
85 function reduceScopeVerbosity(data, sanitize = true) {
86 let unclean = false;
87
88 const {
89 scopesEntries: ctxScopesEntries,
90 profilesEntries: ctxProfilesEntries,
91 needsSanitize: ctxNeedsSanitize,
92 } = _scopesFrom(data?.ctx?.profilesScopes);
93
94 const {
95 scopesEntries: sessionScopesEntries,
96 profilesEntries: sessionProfilesEntries,
97 needsSanitize: sessionNeedsSanitize,
98 } = _scopesFrom(data?.ctx?.session);
99
100 if (ctxNeedsSanitize || sessionNeedsSanitize) {
101 unclean = true;
102 }
103 if (unclean && sanitize) {
104 if (ctxNeedsSanitize) {
105 Object.assign(data.ctx.profilesScopes, _sanitizeProfilesScopes(ctxScopesEntries, ctxProfilesEntries));
106 }
107 if (sessionNeedsSanitize) {
108 Object.assign(data.ctx.session, _sanitizeProfilesScopes(sessionScopesEntries, sessionProfilesEntries));
109 }
110 }
111
112 return unclean;
113 }
114
115
116 /**
117 * Return any scope entries on an object, and whether sanitization is needed.
118 * @param {object=} obj obj
119 * @returns {object} obj
120 */
121 const _scopesFrom = (obj) => {
122 const scopesEntries = Object.entries(obj?.scopeIndex || {});
123 const profilesEntries = Object.entries(obj?.profileScopes || {});
124 const needsSanitize = scopesEntries.length || profilesEntries.length;
125 return {
126 scopesEntries,
127 profilesEntries,
128 needsSanitize,
129 };
130 };
131
132
133 /**
134 * @typedef {[string, object]} ScopeEntry
135 */
136 /**
137 * Return new list of entries with scrubbed scopeDetails.
138 * @param {ScopeEntry[]} entries entries
139 * @returns {ScopeEntry[]} entries
140 */
141 const _scopeEntriesScrubber = (entries) => entries.map(([scopeName, scopeDetails]) => ([scopeName, { profiles: scopeDetails.profiles }]));
142
143
144 /**
145 * Create a new profilesScopes type object with scrubbed scope details.
146 * @param {ScopeEntry[]} scopesEntries entries
147 * @param {ScopeEntry[]} profilesEntries entries
148 * @returns {object} profilesScopes
149 */
150 const _sanitizeProfilesScopes = (scopesEntries, profilesEntries) => {
151 const referencedScopesEntries = scopesEntries.filter(([_scopeName, scopeDetails]) => scopeDetails?.profiles?.length); // eslint-disable-line no-unused-vars
152 const scrubbedScopesEntries = _scopeEntriesScrubber(referencedScopesEntries);
153
154 const scrubbedProfilesEntries = profilesEntries.map(([profileName, profileScopes]) => {
155 const profileScopeEntries = Object.entries(profileScopes);
156 const scrubbedProfileScopeEntries = _scopeEntriesScrubber(profileScopeEntries);
157 const scrubbedProfileScopes = Object.fromEntries(scrubbedProfileScopeEntries);
158 return [profileName, scrubbedProfileScopes];
159 });
160
161 return {
162 scopeIndex: Object.fromEntries(scrubbedScopesEntries),
163 profileScopes: Object.fromEntries(scrubbedProfilesEntries),
164 };
165 };
166
167 module.exports = {
168 sanitizePostCredential,
169 sanitizeContext,
170 reduceScopeVerbosity,
171 };