adminforth 1.1.92 → 1.1.93

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.
Files changed (39) hide show
  1. package/dataConnectors/baseConnector.ts +2 -2
  2. package/dataConnectors/mongo.ts +2 -2
  3. package/dataConnectors/postgres.ts +2 -2
  4. package/dataConnectors/sqlite.ts +2 -2
  5. package/dist/plugins/TwoFactorsAuthPlugin/index.js +2 -3
  6. package/dist/spa/spa/src/components/Toast.vue +1 -1
  7. package/index.ts +5 -5
  8. package/modules/codeInjector.ts +2 -2
  9. package/package.json +2 -2
  10. package/plugins/AuditLogPlugin/index.ts +3 -3
  11. package/plugins/ForeignInlineListPlugin/index.ts +4 -4
  12. package/plugins/S3UploadPlugin/custom/s3uploader.vue +24 -4
  13. package/plugins/S3UploadPlugin/index.ts +4 -4
  14. package/plugins/S3UploadPlugin/package.json +1 -1
  15. package/plugins/TwoFactorsAuthPlugin/dist/auth.js +108 -0
  16. package/plugins/TwoFactorsAuthPlugin/dist/dataConnectors/baseConnector.js +90 -0
  17. package/plugins/TwoFactorsAuthPlugin/dist/dataConnectors/mongo.js +191 -0
  18. package/plugins/TwoFactorsAuthPlugin/dist/dataConnectors/postgres.js +295 -0
  19. package/plugins/TwoFactorsAuthPlugin/dist/dataConnectors/sqlite.js +246 -0
  20. package/plugins/TwoFactorsAuthPlugin/dist/index.js +1186 -0
  21. package/plugins/TwoFactorsAuthPlugin/dist/modules/codeInjector.js +546 -0
  22. package/plugins/TwoFactorsAuthPlugin/dist/modules/styleGenerator.js +43 -0
  23. package/plugins/TwoFactorsAuthPlugin/dist/modules/styles.js +92 -0
  24. package/plugins/TwoFactorsAuthPlugin/dist/modules/utils.js +301 -0
  25. package/plugins/TwoFactorsAuthPlugin/dist/plugins/TwoFactorsAuthPlugin/index.js +149 -0
  26. package/plugins/TwoFactorsAuthPlugin/dist/plugins/TwoFactorsAuthPlugin/types.js +1 -0
  27. package/plugins/TwoFactorsAuthPlugin/dist/plugins/base.js +34 -0
  28. package/plugins/TwoFactorsAuthPlugin/dist/servers/express.js +230 -0
  29. package/plugins/TwoFactorsAuthPlugin/dist/types/AdminForthConfig.js +105 -0
  30. package/plugins/TwoFactorsAuthPlugin/index.ts +10 -10
  31. package/plugins/TwoFactorsAuthPlugin/package-lock.json +2 -2
  32. package/plugins/TwoFactorsAuthPlugin/package.json +8 -6
  33. package/plugins/TwoFactorsAuthPlugin/tsconfig.json +112 -0
  34. package/plugins/base.ts +4 -4
  35. package/servers/express.ts +4 -4
  36. package/spa/src/components/Toast.vue +1 -1
  37. package/tsconfig.json +1 -1
  38. package/types/AdminForthConfig.ts +34 -28
  39. package/types/FrontendAPI.ts +2 -1
@@ -0,0 +1,92 @@
1
+ export const styles = () => ({
2
+ colors: {
3
+ lightHtml: "#FFFFFF", // main background
4
+ lightNavbar: "#FFFFFF", // navbar background
5
+ lightNavbarBorder: "rgb(229 231 235)", // border
6
+ lightNavbarText: "#111827", // navbar text
7
+ lightNavbarTextHover: "alias:lightNavbarText darken", // navbar text hover
8
+ lightNavbarTextActive: "alias:lightNavbarText darken", // navbar text active
9
+ lightNavbarIcons: "alias:lightNavbarText opacity:0.7", // navbar icons
10
+ lightSidebar: "#ffffff", // sidebar background
11
+ lightSidebarBorder: "alias:lightSidebarText opacity:0.3", // sidebar right border
12
+ lightSidebarItemHover: "alias:lightSidebarText opacity:0.1", // sidebar list item hover
13
+ lightSidebarItemActive: "alias:lightSidebarText opacity:0.4", // sidebar list item active
14
+ lightSidebarText: "#1f2937", // sidebar list item text
15
+ lightSidebarTextHover: "alias:lightSidebarText darken", // sidebar list item text hover
16
+ lightSidebarTextActive: "alias:lightSidebarText darken", // sidebar list item text active
17
+ lightSidebarDevider: "alias:lightSidebarText opacity:0.3", // sidebar devider
18
+ lightSidebarIcons: "alias:lightSidebarText opacity:0.7", // sidebar list item icons
19
+ lightSidebarIconsHover: "alias:lightSidebarText", // sidebar list item icons hover
20
+ lightSidebarHeading: "alias:lightSidebarText opacity:0.3", // sidebar heading
21
+ lightList: "#FFFFFF", // list view background
22
+ lightListTable: "#FFFFFF", // list view table background
23
+ lightListTableHeading: "rgb(249 250 251)", // list view table heading
24
+ lightListTableHeadingText: "#333333", // list view table heading text
25
+ lightListTableText: "#333333", // list view table text
26
+ lightListTableRowHover: "rgb(249 250 251)", // list view row hover
27
+ lightListBreadcrumbsText: "#666666", // list view breadcrumbs text
28
+ lightListBorder: "#DDDDDD", // list view rows border
29
+ // lightListButton: "#FFFFFF", // list view button
30
+ // lightListButtonBorder: "#d1d5db", // list view button border
31
+ // lightListButtonHover: "#f3f4f6", // list view button hover
32
+ // lightListButtonBorderHover: "#f3f4f6", // list view button border hover
33
+ // lightListButtonActive: "#f3f4f6", // list view button active
34
+ // lightListButtonDisabled: "#f3f4f6", // list view button disabled
35
+ // lightListButtonDisabledText: "#f3f4f6", // list view button disabled text
36
+ // lightListButtonIcon: "#333333", // list view button icon
37
+ lightForm: "#FFFFFF", // show view background
38
+ lightFormBorder: "#F5F5F5", // show view rows border
39
+ lightFormHeading: "rgb(249 250 251)", // show view heading
40
+ lightButtons: "#FFFFFF", // button background
41
+ lightButtonsBorder: "#DDDDDD", // button border
42
+ lightButtonsText: "#111827", // button text
43
+ lightButtonsHover: "#f3f4f6", // button hover
44
+ lightButtonsBorderHover: "#f3f4f6", // button border hover
45
+ lightButtonsActive: "#f3f4f6", // button active
46
+ lightButtonsDisabled: "#f3f4f6", // button disabled
47
+ lightButtonsDisabledText: "#f3f4f6", // button disabled text
48
+ lightButtonsIcon: "#333333", // button icon
49
+ // colors for dark theme
50
+ darkHtml: "#111827",
51
+ darkNavbar: "#111827",
52
+ darkNavbarBorder: "#e5e7eb",
53
+ darkNavbarText: "#9ca3af",
54
+ darkNavbarTextHover: "alias:darkNavbarText lighten",
55
+ darkNavbarTextActive: "alias:darkNavbarText lighten",
56
+ darkNavbarIcons: "alias:darkNavbarText opacity:0.7",
57
+ darkSidebar: "#1f2937",
58
+ darkSidebarBorder: "alias:darkSidebarText opacity:0.3",
59
+ darkSidebarItemHover: "alias:darkSidebarText opacity:0.1",
60
+ darkSidebarItemActive: "alias:darkSidebarText opacity:0.4",
61
+ darkSidebarText: "#9ca3af",
62
+ darkSidebarTextHover: "alias:darkSidebarText lighten",
63
+ darkSidebarTextActive: "alias:darkSidebarText lighten",
64
+ darkSidebarDevider: "alias:darkSidebarText opacity:0.3",
65
+ darkSidebarIcons: "alias:darkSidebarText opacity:0.7",
66
+ darkSidebarIconsHover: "alias:darkSidebarText",
67
+ darkSidebarHeading: "alias:darkSidebarText opacity:0.3",
68
+ darkList: "#111111",
69
+ darkListTable: "#1f2937",
70
+ darkListTableHeading: "#334155",
71
+ darkListTableHeadingText: "#9ca3af",
72
+ darkListTableText: "#9ca3af",
73
+ darkListTableRowHover: "#4b5563",
74
+ darkListBreadcrumbsText: "#BBBBBB",
75
+ darkListBorder: "#444444",
76
+ darkForm: "#111111",
77
+ darkFormBorder: "#222222",
78
+ darkFormHeading: "#334155"
79
+ },
80
+ boxShadow: {
81
+ customLight: "0 4px 8px rgba(0, 0, 0, 0.1)", // Lighter shadow
82
+ headerShadow: "0 4px 8px rgba(0, 0, 0, 0.1)", // Lighter shadow
83
+ listTableShadow: "0 4px 8px rgba(0, 0, 0, 0.1)", // Lighter shadow
84
+ resourseFormShadow: "0 4px 8px rgba(0, 0, 0, 0.1)" // Lighter shadow
85
+ },
86
+ fontSize: {
87
+ "headerText-size": "1rem"
88
+ },
89
+ borderRadius: {
90
+ "default": "2px"
91
+ }
92
+ });
@@ -0,0 +1,301 @@
1
+ import path from 'path';
2
+ import { fileURLToPath } from 'url';
3
+ import fs from 'fs';
4
+ // @ts-ignore-next-line
5
+ const csscolors = {
6
+ "aliceblue": "#f0f8ff",
7
+ "antiquewhite": "#faebd7",
8
+ "aqua": "#00ffff",
9
+ "aquamarine": "#7fffd4",
10
+ "azure": "#f0ffff",
11
+ "beige": "#f5f5dc",
12
+ "bisque": "#ffe4c4",
13
+ "black": "#000000",
14
+ "blanchedalmond": "#ffebcd",
15
+ "blue": "#0000ff",
16
+ "blueviolet": "#8a2be2",
17
+ "brown": "#a52a2a",
18
+ "burlywood": "#deb887",
19
+ "cadetblue": "#5f9ea0",
20
+ "chartreuse": "#7fff00",
21
+ "chocolate": "#d2691e",
22
+ "coral": "#ff7f50",
23
+ "cornflowerblue": "#6495ed",
24
+ "cornsilk": "#fff8dc",
25
+ "crimson": "#dc143c",
26
+ "cyan": "#00ffff",
27
+ "darkblue": "#00008b",
28
+ "darkcyan": "#008b8b",
29
+ "darkgoldenrod": "#b8860b",
30
+ "darkgray": "#a9a9a9",
31
+ "darkgreen": "#006400",
32
+ "darkgrey": "#a9a9a9",
33
+ "darkkhaki": "#bdb76b",
34
+ "darkmagenta": "#8b008b",
35
+ "darkolivegreen": "#556b2f",
36
+ "darkorange": "#ff8c00",
37
+ "darkorchid": "#9932cc",
38
+ "darkred": "#8b0000",
39
+ "darksalmon": "#e9967a",
40
+ "darkseagreen": "#8fbc8f",
41
+ "darkslateblue": "#483d8b",
42
+ "darkslategray": "#2f4f4f",
43
+ "darkslategrey": "#2f4f4f",
44
+ "darkturquoise": "#00ced1",
45
+ "darkviolet": "#9400d3",
46
+ "deeppink": "#ff1493",
47
+ "deepskyblue": "#00bfff",
48
+ "dimgray": "#696969",
49
+ "dimgrey": "#696969",
50
+ "dodgerblue": "#1e90ff",
51
+ "firebrick": "#b22222",
52
+ "floralwhite": "#fffaf0",
53
+ "forestgreen": "#228b22",
54
+ "fuchsia": "#ff00ff",
55
+ "gainsboro": "#dcdcdc",
56
+ "ghostwhite": "#f8f8ff",
57
+ "goldenrod": "#daa520",
58
+ "gold": "#ffd700",
59
+ "gray": "#808080",
60
+ "green": "#008000",
61
+ "greenyellow": "#adff2f",
62
+ "grey": "#808080",
63
+ "honeydew": "#f0fff0",
64
+ "hotpink": "#ff69b4",
65
+ "indianred": "#cd5c5c",
66
+ "indigo": "#4b0082",
67
+ "ivory": "#fffff0",
68
+ "khaki": "#f0e68c",
69
+ "lavenderblush": "#fff0f5",
70
+ "lavender": "#e6e6fa",
71
+ "lawngreen": "#7cfc00",
72
+ "lemonchiffon": "#fffacd",
73
+ "lightblue": "#add8e6",
74
+ "lightcoral": "#f08080",
75
+ "lightcyan": "#e0ffff",
76
+ "lightgoldenrodyellow": "#fafad2",
77
+ "lightgray": "#d3d3d3",
78
+ "lightgreen": "#90ee90",
79
+ "lightgrey": "#d3d3d3",
80
+ "lightpink": "#ffb6c1",
81
+ "lightsalmon": "#ffa07a",
82
+ "lightseagreen": "#20b2aa",
83
+ "lightskyblue": "#87cefa",
84
+ "lightslategray": "#778899",
85
+ "lightslategrey": "#778899",
86
+ "lightsteelblue": "#b0c4de",
87
+ "lightyellow": "#ffffe0",
88
+ "lime": "#00ff00",
89
+ "limegreen": "#32cd32",
90
+ "linen": "#faf0e6",
91
+ "magenta": "#ff00ff",
92
+ "maroon": "#800000",
93
+ "mediumaquamarine": "#66cdaa",
94
+ "mediumblue": "#0000cd",
95
+ "mediumorchid": "#ba55d3",
96
+ "mediumpurple": "#9370db",
97
+ "mediumseagreen": "#3cb371",
98
+ "mediumslateblue": "#7b68ee",
99
+ "mediumspringgreen": "#00fa9a",
100
+ "mediumturquoise": "#48d1cc",
101
+ "mediumvioletred": "#c71585",
102
+ "midnightblue": "#191970",
103
+ "mintcream": "#f5fffa",
104
+ "mistyrose": "#ffe4e1",
105
+ "moccasin": "#ffe4b5",
106
+ "navajowhite": "#ffdead",
107
+ "navy": "#000080",
108
+ "oldlace": "#fdf5e6",
109
+ "olive": "#808000",
110
+ "olivedrab": "#6b8e23",
111
+ "orange": "#ffa500",
112
+ "orangered": "#ff4500",
113
+ "orchid": "#da70d6",
114
+ "palegoldenrod": "#eee8aa",
115
+ "palegreen": "#98fb98",
116
+ "paleturquoise": "#afeeee",
117
+ "palevioletred": "#db7093",
118
+ "papayawhip": "#ffefd5",
119
+ "peachpuff": "#ffdab9",
120
+ "peru": "#cd853f",
121
+ "pink": "#ffc0cb",
122
+ "plum": "#dda0dd",
123
+ "powderblue": "#b0e0e6",
124
+ "purple": "#800080",
125
+ "rebeccapurple": "#663399",
126
+ "red": "#ff0000",
127
+ "rosybrown": "#bc8f8f",
128
+ "royalblue": "#4169e1",
129
+ "saddlebrown": "#8b4513",
130
+ "salmon": "#fa8072",
131
+ "sandybrown": "#f4a460",
132
+ "seagreen": "#2e8b57",
133
+ "seashell": "#fff5ee",
134
+ "sienna": "#a0522d",
135
+ "silver": "#c0c0c0",
136
+ "skyblue": "#87ceeb",
137
+ "slateblue": "#6a5acd",
138
+ "slategray": "#708090",
139
+ "slategrey": "#708090",
140
+ "snow": "#fffafa",
141
+ "springgreen": "#00ff7f",
142
+ "steelblue": "#4682b4",
143
+ "tan": "#d2b48c",
144
+ "teal": "#008080",
145
+ "thistle": "#d8bfd8",
146
+ "tomato": "#ff6347",
147
+ "turquoise": "#40e0d0",
148
+ "violet": "#ee82ee",
149
+ "wheat": "#f5deb3",
150
+ "white": "#ffffff",
151
+ "whitesmoke": "#f5f5f5",
152
+ "yellow": "#ffff00",
153
+ "yellowgreen": "#9acd32"
154
+ };
155
+ export function guessLabelFromName(name) {
156
+ if (name.includes('_')) {
157
+ return name.split('_').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
158
+ }
159
+ else if (name.includes('-')) {
160
+ return name.split('-').map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
161
+ }
162
+ else {
163
+ // split by capital letters
164
+ return name.split(/(?=[A-Z])/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(' ');
165
+ }
166
+ }
167
+ export const currentFileDir = (metaUrl) => {
168
+ const __filename = fileURLToPath(metaUrl);
169
+ let __dirname = path.dirname(__filename);
170
+ if (__dirname.endsWith('/dist/modules')) {
171
+ // in prod build we are in dist also, so make another back jump to go true sorces
172
+ __dirname = path.join(__dirname, '..');
173
+ }
174
+ return __dirname;
175
+ };
176
+ export const ADMIN_FORTH_ABSOLUTE_PATH = path.join(currentFileDir(import.meta.url), '..');
177
+ const package_json = JSON.parse(fs.readFileSync(path.join(ADMIN_FORTH_ABSOLUTE_PATH, 'package.json'), 'utf8'));
178
+ export const ADMINFORTH_VERSION = package_json.version;
179
+ export function getComponentNameFromPath(filePath) {
180
+ return filePath.replace(/@/g, '').replace(/\./g, '').replace(/\//g, '');
181
+ }
182
+ export function listify(param) {
183
+ return param || [];
184
+ }
185
+ export function deepMerge(target, source) {
186
+ if (typeof target !== 'object' || target === null) {
187
+ return source;
188
+ }
189
+ for (let key in source) {
190
+ if (source.hasOwnProperty(key)) {
191
+ if (typeof source[key] === 'object' && source[key] !== null) {
192
+ if (!target[key]) {
193
+ target[key] = {};
194
+ }
195
+ deepMerge(target[key], source[key]);
196
+ }
197
+ else {
198
+ target[key] = source[key];
199
+ }
200
+ }
201
+ }
202
+ return target;
203
+ }
204
+ function toCamelCase(str) {
205
+ return str.replace(/[-_](.)/g, (_, char) => char.toUpperCase());
206
+ }
207
+ export function transformObject(obj, parentKey = '', result = {}) {
208
+ for (const key in obj) {
209
+ if (obj.hasOwnProperty(key)) {
210
+ const camelCaseKey = toCamelCase(parentKey ? `${parentKey}_${key}` : key);
211
+ if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
212
+ transformObject(obj[key], camelCaseKey, result);
213
+ }
214
+ else {
215
+ result[camelCaseKey] = obj[key];
216
+ }
217
+ }
218
+ }
219
+ //remove 'Default' from keys
220
+ for (const key in result) {
221
+ if (key.includes('Main')) {
222
+ const newKey = key.replace('Main', '');
223
+ result[newKey] = result[key];
224
+ delete result[key];
225
+ }
226
+ }
227
+ return result;
228
+ }
229
+ function hexToRGBA(hex, opacity) {
230
+ var c;
231
+ if (/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)) {
232
+ c = hex.substring(1).split('');
233
+ if (c.length == 3) {
234
+ c = [c[0], c[0], c[1], c[1], c[2], c[2]];
235
+ }
236
+ c = '0x' + c.join('');
237
+ return 'rgba(' + [(c >> 16) & 255, (c >> 8) & 255, c & 255].join(',') + ',' + opacity + ')';
238
+ }
239
+ throw new Error('Bad Hex');
240
+ }
241
+ export function createRGBA(color, opacity) {
242
+ if (!color)
243
+ return;
244
+ else if (color.startsWith("rgba")) {
245
+ //add opacity to existing rgba color
246
+ const rgb = color.match(/[\d.]+/g);
247
+ console.log('rgb', rgb);
248
+ return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${Number(rgb[3]) * opacity})`;
249
+ }
250
+ else if (color.startsWith("rgb")) {
251
+ const rgb = color.match(/\d+/g);
252
+ return `rgba(${rgb[0]}, ${rgb[1]}, ${rgb[2]}, ${opacity})`;
253
+ }
254
+ else if (color.startsWith("#")) {
255
+ return hexToRGBA(color, opacity);
256
+ }
257
+ else if (csscolors[color]) {
258
+ return hexToRGBA(csscolors[color], opacity);
259
+ }
260
+ else {
261
+ return color;
262
+ }
263
+ }
264
+ export function parseColorForAliases(str) {
265
+ const aliasRegex = /alias:([^ ]+)/;
266
+ const opacityRegex = /opacity:([0-9.]+)/;
267
+ const darkenRegex = /darken/;
268
+ const lightenRegex = /lighten/;
269
+ // Extract alias and properties
270
+ const aliasMatch = str.match(aliasRegex);
271
+ const opacityMatch = str.match(opacityRegex);
272
+ const darkenMatch = str.match(darkenRegex);
273
+ const lightenMatch = str.match(lightenRegex);
274
+ return { aliasMatch, opacityMatch, darkenMatch, lightenMatch };
275
+ }
276
+ export function darkenRGBA(rgba) {
277
+ let amount = 0.2;
278
+ // Extract the RGBA components
279
+ let [r, g, b, a] = rgba.match(/\d+/g).map(Number);
280
+ // Ensure amount is between 0 and 1
281
+ amount = Math.max(0, Math.min(1, amount));
282
+ // Calculate the new RGB values
283
+ r = Math.max(0, r * (1 - amount));
284
+ g = Math.max(0, g * (1 - amount));
285
+ b = Math.max(0, b * (1 - amount));
286
+ // Return the new RGBA color
287
+ return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${a})`;
288
+ }
289
+ export function lightenRGBA(rgba) {
290
+ let amount = 0.2;
291
+ // Extract the RGBA components
292
+ let [r, g, b, a] = rgba.match(/\d+/g).map(Number);
293
+ // Ensure amount is between 0 and 1
294
+ amount = Math.max(0, Math.min(1, amount));
295
+ // Calculate the new RGB values
296
+ r = Math.min(255, r * (1 + amount));
297
+ g = Math.min(255, g * (1 + amount));
298
+ b = Math.min(255, b * (1 + amount));
299
+ // Return the new RGBA color
300
+ return `rgba(${Math.round(r)}, ${Math.round(g)}, ${Math.round(b)}, ${a})`;
301
+ }
@@ -0,0 +1,149 @@
1
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
2
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
3
+ return new (P || (P = Promise))(function (resolve, reject) {
4
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
5
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
6
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
7
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
8
+ });
9
+ };
10
+ import AdminForthPlugin from "../base.js";
11
+ import twofactor from 'node-2fa';
12
+ export default class TwoFactorsAuthPlugin extends AdminForthPlugin {
13
+ constructor(options) {
14
+ super(options, import.meta.url);
15
+ this.options = options;
16
+ }
17
+ modifyResourceConfig(adminforth, resourceConfig) {
18
+ super.modifyResourceConfig(adminforth, resourceConfig);
19
+ this.adminforth = adminforth;
20
+ this.adminForthAuth = adminforth.auth;
21
+ const customPages = this.adminforth.config.customization.customPages;
22
+ customPages.push({
23
+ path: '/confirm2fa',
24
+ component: { file: this.componentPath('TwoFactorsConfirmation.vue'), meta: { customLayout: true } }
25
+ });
26
+ customPages.push({
27
+ path: '/setup2fa',
28
+ component: { file: this.componentPath('TwoFactorsSetup.vue'), meta: { title: 'Setup 2FA', customLayout: true } }
29
+ });
30
+ this.activate(resourceConfig, adminforth);
31
+ }
32
+ activate(resourceConfig, adminforth) {
33
+ if (!this.options.twoFaSecretFieldName) {
34
+ throw new Error('twoFaSecretFieldName is required');
35
+ }
36
+ if (typeof this.options.twoFaSecretFieldName !== 'string') {
37
+ throw new Error('twoFaSecretFieldName must be a string');
38
+ }
39
+ this.authResource = resourceConfig;
40
+ if (!this.authResource.columns.some((col) => col.name === this.options.twoFaSecretFieldName)) {
41
+ throw new Error(`Column ${this.options.twoFaSecretFieldName} not found in ${this.authResource.label}`);
42
+ }
43
+ const beforeLoginConfirmation = this.adminforth.config.auth;
44
+ if (!beforeLoginConfirmation.beforeLoginConfirmation) {
45
+ beforeLoginConfirmation.beforeLoginConfirmation = [];
46
+ }
47
+ beforeLoginConfirmation.beforeLoginConfirmation.push((_a) => __awaiter(this, [_a], void 0, function* ({ adminUser }) {
48
+ if (adminUser.isRoot) {
49
+ return { body: { loginAllowed: true }, ok: true };
50
+ }
51
+ const secret = adminUser.dbUser[this.options.twoFaSecretFieldName];
52
+ const userName = adminUser.dbUser[adminforth.config.auth.usernameField];
53
+ const brandName = adminforth.config.customization.brandName;
54
+ const authResource = adminforth.config.resources.find((res) => res.resourceId === adminforth.config.auth.resourceId);
55
+ const authPk = authResource.columns.find((col) => col.primaryKey).name;
56
+ const userPk = adminUser.dbUser[authPk];
57
+ let newSecret = null;
58
+ if (!secret) {
59
+ const tempSecret = twofactor.generateSecret({ name: brandName, account: userName });
60
+ newSecret = tempSecret.secret;
61
+ }
62
+ else {
63
+ return {
64
+ body: {
65
+ loginAllowed: false,
66
+ redirectTo: '/confirm2fa',
67
+ setCookie: [{
68
+ name: 'totpTemporaryJWT',
69
+ value: this.adminforth.auth.issueJWT({ userName, issuer: brandName, pk: userPk }, 'tempTotp'),
70
+ expiry: '1h',
71
+ httpOnly: true
72
+ },]
73
+ },
74
+ ok: true
75
+ };
76
+ }
77
+ const totpTemporaryJWT = this.adminforth.auth.issueJWT({ userName, newSecret, issuer: brandName, pk: userPk }, 'tempTotp');
78
+ return {
79
+ body: {
80
+ loginAllowed: false,
81
+ redirectTo: secret ? '/confirm2fa' : '/setup2fa',
82
+ setCookie: [{
83
+ name: 'totpTemporaryJWT',
84
+ value: totpTemporaryJWT,
85
+ expiry: '1h',
86
+ httpOnly: true
87
+ },]
88
+ },
89
+ ok: true
90
+ };
91
+ }));
92
+ }
93
+ setupEndpoints(server) {
94
+ server.endpoint({
95
+ method: 'POST',
96
+ path: `/plugin/twofa/initSetup`,
97
+ noAuth: true,
98
+ handler: (server) => __awaiter(this, void 0, void 0, function* () {
99
+ const toReturn = { totpJWT: null, status: 'ok', };
100
+ const totpTemporaryJWT = server.cookies['adminforth_totpTemporaryJWT'];
101
+ if (totpTemporaryJWT) {
102
+ toReturn.totpJWT = totpTemporaryJWT;
103
+ }
104
+ return toReturn;
105
+ })
106
+ });
107
+ server.endpoint({
108
+ method: 'POST',
109
+ path: `/plugin/twofa/confirmSetup`,
110
+ noAuth: true,
111
+ handler: (_a) => __awaiter(this, [_a], void 0, function* ({ body, adminUser, response, cookies }) {
112
+ const totpTemporaryJWT = cookies['adminforth_totpTemporaryJWT'];
113
+ const decoded = yield this.adminforth.auth.verify(totpTemporaryJWT, 'tempTotp');
114
+ if (!decoded) {
115
+ return { status: 'error', message: 'Invalid token' };
116
+ }
117
+ if (decoded.newSecret) {
118
+ const verified = twofactor.verifyToken(decoded.newSecret, body.code);
119
+ if (verified) {
120
+ this.connectors = this.adminforth.connectors;
121
+ const connector = this.connectors[this.authResource.dataSource];
122
+ yield connector.updateRecord({ resource: this.authResource, recordId: decoded.pk, newValues: { [this.options.twoFaSecretFieldName]: decoded.newSecret } });
123
+ this.adminForthAuth.removeCustomCookie({ response, name: 'totpTemporaryJWT' });
124
+ this.adminForthAuth.setAuthCookie({ response, username: decoded.userName, pk: decoded.pk });
125
+ return { status: 'ok', allowedLogin: true };
126
+ }
127
+ else {
128
+ return { error: 'Wrong or expired OTP code' };
129
+ }
130
+ }
131
+ else {
132
+ // user already has secret, get it
133
+ this.connectors = this.adminforth.connectors;
134
+ const connector = this.connectors[this.authResource.dataSource];
135
+ const user = yield connector.getRecordByPrimaryKey(this.authResource, decoded.pk);
136
+ const verified = twofactor.verifyToken(user[this.options.twoFaSecretFieldName], body.code);
137
+ if (verified) {
138
+ this.adminForthAuth.removeCustomCookie({ response, name: 'totpTemporaryJWT' });
139
+ this.adminForthAuth.setAuthCookie({ response, username: decoded.userName, pk: decoded.pk });
140
+ return { status: 'ok', allowedLogin: true };
141
+ }
142
+ else {
143
+ return { error: 'Wrong or expired OTP code' };
144
+ }
145
+ }
146
+ })
147
+ });
148
+ }
149
+ }
@@ -0,0 +1,34 @@
1
+ import { getComponentNameFromPath } from '../modules/utils.js';
2
+ import { currentFileDir } from '../modules/utils.js';
3
+ import path from 'path';
4
+ import fs from 'fs';
5
+ import { v4 as uuidv4 } from 'uuid';
6
+ export default class AdminForthPlugin {
7
+ constructor(pluginOptions, metaUrl) {
8
+ this.customFolderName = 'custom';
9
+ // set up plugin here
10
+ this.pluginDir = currentFileDir(metaUrl);
11
+ this.pluginInstanceId = uuidv4();
12
+ }
13
+ setupEndpoints(server) {
14
+ }
15
+ modifyResourceConfig(adminforth, resourceConfig) {
16
+ this.adminforth = adminforth;
17
+ }
18
+ componentPath(componentFile) {
19
+ const key = `@@/plugins/${this.constructor.name}/${componentFile}`;
20
+ const componentName = getComponentNameFromPath(key);
21
+ const absSrcFolder = path.join(this.pluginDir, this.customFolderName);
22
+ if (!this.adminforth.codeInjector.srcFoldersToSync[absSrcFolder]) {
23
+ this.adminforth.codeInjector.srcFoldersToSync[absSrcFolder] = `./plugins/${this.constructor.name}/`;
24
+ }
25
+ if (!this.adminforth.codeInjector.allComponentNames[key]) {
26
+ const absSrcPath = path.join(absSrcFolder, componentFile);
27
+ if (!fs.existsSync(absSrcPath)) {
28
+ throw new Error(`Plugin "${this.constructor.name}" tried to use file as component which does not exist at "${absSrcPath}"`);
29
+ }
30
+ this.adminforth.codeInjector.allComponentNames[key] = componentName;
31
+ }
32
+ return key;
33
+ }
34
+ }