adminforth 1.2.98 → 1.3.1
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/auth.ts +6 -5
- package/dataConnectors/baseConnector.ts +46 -17
- package/dataConnectors/clickhouse.ts +58 -37
- package/dataConnectors/mongo.ts +45 -14
- package/dataConnectors/postgres.ts +53 -36
- package/dataConnectors/sqlite.ts +37 -35
- package/dist/auth.js +6 -6
- package/dist/dataConnectors/baseConnector.js +32 -10
- package/dist/dataConnectors/clickhouse.js +59 -48
- package/dist/dataConnectors/mongo.js +29 -12
- package/dist/dataConnectors/postgres.js +62 -52
- package/dist/dataConnectors/sqlite.js +52 -45
- package/dist/index.js +36 -15
- package/dist/modules/configValidator.js +10 -14
- package/dist/modules/operationalResource.js +72 -0
- package/dist/modules/restApi.js +75 -90
- package/dist/modules/utils.js +18 -0
- package/dist/types/AdminForthConfig.js +37 -0
- package/index.ts +45 -17
- package/modules/configValidator.ts +11 -16
- package/modules/operationalResource.ts +73 -0
- package/modules/restApi.ts +59 -72
- package/modules/utils.ts +22 -0
- package/package.json +2 -1
- package/types/AdminForthConfig.ts +114 -35
|
@@ -53,6 +53,43 @@ export var AdminForthResourcePages;
|
|
|
53
53
|
AdminForthResourcePages["create"] = "create";
|
|
54
54
|
AdminForthResourcePages["filter"] = "filter";
|
|
55
55
|
})(AdminForthResourcePages || (AdminForthResourcePages = {}));
|
|
56
|
+
export class Filters {
|
|
57
|
+
static EQ(field, value) {
|
|
58
|
+
return { field, operator: AdminForthFilterOperators.EQ, value };
|
|
59
|
+
}
|
|
60
|
+
static NEQ(field, value) {
|
|
61
|
+
return { field, operator: AdminForthFilterOperators.NE, value };
|
|
62
|
+
}
|
|
63
|
+
static GT(field, value) {
|
|
64
|
+
return { field, operator: AdminForthFilterOperators.GT, value };
|
|
65
|
+
}
|
|
66
|
+
static GTE(field, value) {
|
|
67
|
+
return { field, operator: AdminForthFilterOperators.GTE, value };
|
|
68
|
+
}
|
|
69
|
+
static LT(field, value) {
|
|
70
|
+
return { field, operator: AdminForthFilterOperators.LT, value };
|
|
71
|
+
}
|
|
72
|
+
static LTE(field, value) {
|
|
73
|
+
return { field, operator: AdminForthFilterOperators.LTE, value };
|
|
74
|
+
}
|
|
75
|
+
static IN(field, value) {
|
|
76
|
+
return { field, operator: AdminForthFilterOperators.IN, value };
|
|
77
|
+
}
|
|
78
|
+
static NOT_IN(field, value) {
|
|
79
|
+
return { field, operator: AdminForthFilterOperators.NIN, value };
|
|
80
|
+
}
|
|
81
|
+
static LIKE(field, value) {
|
|
82
|
+
return { field, operator: AdminForthFilterOperators.LIKE, value };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export class Sorts {
|
|
86
|
+
static ASC(field) {
|
|
87
|
+
return { field, direction: AdminForthSortDirections.asc };
|
|
88
|
+
}
|
|
89
|
+
static DESC(field) {
|
|
90
|
+
return { field, direction: AdminForthSortDirections.desc };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
56
93
|
export var AllowedActionsEnum;
|
|
57
94
|
(function (AllowedActionsEnum) {
|
|
58
95
|
AllowedActionsEnum["show"] = "show";
|
package/index.ts
CHANGED
|
@@ -5,12 +5,14 @@ import PostgresConnector from './dataConnectors/postgres.js';
|
|
|
5
5
|
import SQLiteConnector from './dataConnectors/sqlite.js';
|
|
6
6
|
import CodeInjector from './modules/codeInjector.js';
|
|
7
7
|
import ExpressServer from './servers/express.js';
|
|
8
|
-
import { ADMINFORTH_VERSION, listify } from './modules/utils.js';
|
|
8
|
+
import { ADMINFORTH_VERSION, listify, suggestIfTypo } from './modules/utils.js';
|
|
9
9
|
import {
|
|
10
10
|
type AdminForthConfig,
|
|
11
11
|
type IAdminForth,
|
|
12
12
|
type IConfigValidator,
|
|
13
|
-
|
|
13
|
+
IOperationalResource,
|
|
14
|
+
AdminForthFilterOperators,
|
|
15
|
+
AdminForthDataTypes, AdminForthResourcePages, IHttpServer,
|
|
14
16
|
BeforeSaveFunction,
|
|
15
17
|
AfterSaveFunction,
|
|
16
18
|
AdminUser,
|
|
@@ -20,11 +22,13 @@ import AdminForthPlugin from './basePlugin.js';
|
|
|
20
22
|
import ConfigValidator from './modules/configValidator.js';
|
|
21
23
|
import AdminForthRestAPI, { interpretResource } from './modules/restApi.js';
|
|
22
24
|
import ClickhouseConnector from './dataConnectors/clickhouse.js';
|
|
25
|
+
import OperationalResource from './modules/operationalResource.js';
|
|
23
26
|
|
|
24
27
|
// exports
|
|
25
28
|
export * from './types/AdminForthConfig.js';
|
|
26
29
|
export { interpretResource };
|
|
27
30
|
export { AdminForthPlugin };
|
|
31
|
+
export { suggestIfTypo };
|
|
28
32
|
|
|
29
33
|
|
|
30
34
|
class AdminForth implements IAdminForth {
|
|
@@ -50,7 +54,9 @@ class AdminForth implements IAdminForth {
|
|
|
50
54
|
activatedPlugins: Array<AdminForthPlugin>;
|
|
51
55
|
configValidator: IConfigValidator;
|
|
52
56
|
restApi: AdminForthRestAPI;
|
|
53
|
-
|
|
57
|
+
operationalResources: {
|
|
58
|
+
[resourceId: string]: IOperationalResource,
|
|
59
|
+
}
|
|
54
60
|
baseUrlSlashed: string;
|
|
55
61
|
|
|
56
62
|
statuses: {
|
|
@@ -116,21 +122,24 @@ class AdminForth implements IAdminForth {
|
|
|
116
122
|
this.config.dataSources.forEach((ds) => {
|
|
117
123
|
const dbType = ds.url.split(':')[0];
|
|
118
124
|
if (!this.config.databaseConnectors[dbType]) {
|
|
119
|
-
throw new Error(`Database type ${dbType} is not supported, consider using
|
|
125
|
+
throw new Error(`Database type '${dbType}' is not supported, consider using one of ${Object.keys(this.connectorClasses).join(', ')} or create your own data-source connector`);
|
|
120
126
|
}
|
|
121
127
|
this.connectors[ds.id] = new this.config.databaseConnectors[dbType]({url: ds.url});
|
|
122
128
|
});
|
|
123
129
|
|
|
124
130
|
await Promise.all(this.config.resources.map(async (res) => {
|
|
125
131
|
if (!this.connectors[res.dataSource]) {
|
|
126
|
-
|
|
132
|
+
const similar = suggestIfTypo(Object.keys(this.connectors), res.dataSource);
|
|
133
|
+
throw new Error(`Resource '${res.table}' refers to unknown dataSource '${res.dataSource}' ${similar
|
|
134
|
+
? `. Did you mean '${similar}'?` : 'Available dataSources: '+Object.keys(this.connectors).join(', ')}`
|
|
135
|
+
);
|
|
127
136
|
}
|
|
128
137
|
const fieldTypes = await this.connectors[res.dataSource].discoverFields(res);
|
|
129
138
|
if (fieldTypes !== null && !Object.keys(fieldTypes).length) {
|
|
130
139
|
throw new Error(`Table '${res.table}' (In resource '${res.resourceId}') has no fields or does not exist`);
|
|
131
140
|
}
|
|
132
141
|
if (fieldTypes === null) {
|
|
133
|
-
console.error(
|
|
142
|
+
console.error(`⛔ DataSource ${res.dataSource} was not able to perform field discovery. It will not work properly`);
|
|
134
143
|
return;
|
|
135
144
|
}
|
|
136
145
|
if (!res.columns) {
|
|
@@ -139,7 +148,8 @@ class AdminForth implements IAdminForth {
|
|
|
139
148
|
|
|
140
149
|
res.columns.forEach((col, i) => {
|
|
141
150
|
if (!fieldTypes[col.name] && !col.virtual) {
|
|
142
|
-
|
|
151
|
+
const similar = suggestIfTypo(Object.keys(fieldTypes), col.name);
|
|
152
|
+
throw new Error(`Resource '${res.table}' has no column '${col.name}'. ${similar ? `Did you mean '${similar}'?` : ''}`);
|
|
143
153
|
}
|
|
144
154
|
// first find discovered values, but allow override
|
|
145
155
|
res.columns[i] = { ...fieldTypes[col.name], ...col };
|
|
@@ -156,6 +166,11 @@ class AdminForth implements IAdminForth {
|
|
|
156
166
|
|
|
157
167
|
this.statuses.dbDiscover = 'done';
|
|
158
168
|
|
|
169
|
+
this.operationalResources = {};
|
|
170
|
+
this.config.resources.forEach((resource) => {
|
|
171
|
+
this.operationalResources[resource.resourceId] = new OperationalResource(this.connectors[resource.dataSource], resource);
|
|
172
|
+
});
|
|
173
|
+
|
|
159
174
|
// console.log('⚙️⚙️⚙️ Database discovery done', JSON.stringify(this.config.resources, null, 2));
|
|
160
175
|
}
|
|
161
176
|
|
|
@@ -164,9 +179,12 @@ class AdminForth implements IAdminForth {
|
|
|
164
179
|
}
|
|
165
180
|
|
|
166
181
|
async getUserByPk(pk: string) {
|
|
167
|
-
const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.
|
|
182
|
+
const resource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
|
|
168
183
|
if (!resource) {
|
|
169
|
-
|
|
184
|
+
const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId), this.config.auth.usersResourceId);
|
|
185
|
+
throw new Error(`No resource with ${this.config.auth.usersResourceId} found. ${similar ?
|
|
186
|
+
`Did you mean '${similar}' in config.auth.usersResourceId?` : 'Please set correct resource in config.auth.usersResourceId'}`
|
|
187
|
+
);
|
|
170
188
|
}
|
|
171
189
|
const users = await this.connectors[resource.dataSource].getData({
|
|
172
190
|
resource,
|
|
@@ -182,13 +200,6 @@ class AdminForth implements IAdminForth {
|
|
|
182
200
|
|
|
183
201
|
async createResourceRecord({ resource, record, adminUser }: { resource: AdminForthResource, record: any, adminUser: AdminUser }) {
|
|
184
202
|
for (const column of resource.columns) {
|
|
185
|
-
if (column.fillOnCreate) {
|
|
186
|
-
if (record[column.name] === undefined) {
|
|
187
|
-
record[column.name] = column.fillOnCreate({
|
|
188
|
-
initialRecord: record, adminUser
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
203
|
if (
|
|
193
204
|
(column.required as {create?: boolean, edit?: boolean}) ?.create &&
|
|
194
205
|
record[column.name] === undefined &&
|
|
@@ -231,7 +242,7 @@ class AdminForth implements IAdminForth {
|
|
|
231
242
|
}
|
|
232
243
|
const connector = this.connectors[resource.dataSource];
|
|
233
244
|
process.env.HEAVY_DEBUG && console.log('🪲🪲🪲🪲 creating record createResourceRecord', record);
|
|
234
|
-
await connector.createRecord({ resource, record });
|
|
245
|
+
await connector.createRecord({ resource, record, adminUser });
|
|
235
246
|
// execute hook if needed
|
|
236
247
|
for (const hook of listify(resource.hooks?.create?.afterSave as AfterSaveFunction[])) {
|
|
237
248
|
console.log('Hook afterSave', hook);
|
|
@@ -248,6 +259,23 @@ class AdminForth implements IAdminForth {
|
|
|
248
259
|
return { ok: true };
|
|
249
260
|
}
|
|
250
261
|
|
|
262
|
+
resource(resourceId: string) {
|
|
263
|
+
if (this.statuses.dbDiscover !== 'done') {
|
|
264
|
+
if (this.statuses.dbDiscover === 'running') {
|
|
265
|
+
throw new Error('Database discovery is running. You can\'t use data API while database discovery is not finished.\n'+
|
|
266
|
+
'Consider moving your code to a place where it will be executed after database discovery is already done (after await admin.discoverDatabases())');
|
|
267
|
+
} else {
|
|
268
|
+
throw new Error('Database discovery is not yet started. You can\'t use data API before database discovery is done. \n'+
|
|
269
|
+
'Call admin.discoverDatabases() first and await it before using data API');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
if (!this.operationalResources[resourceId]) {
|
|
273
|
+
const closeName = suggestIfTypo(Object.keys(this.operationalResources), resourceId);
|
|
274
|
+
throw new Error(`Resource with id '${resourceId}' not found${closeName ? `. Did you mean '${closeName}'?` : ''}`);
|
|
275
|
+
}
|
|
276
|
+
return this.operationalResources[resourceId];
|
|
277
|
+
}
|
|
278
|
+
|
|
251
279
|
setupEndpoints(server: IHttpServer) {
|
|
252
280
|
this.restApi.registerEndpoints(server);
|
|
253
281
|
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
|
|
11
11
|
import fs from 'fs';
|
|
12
12
|
import path from 'path';
|
|
13
|
-
import { guessLabelFromName } from './utils.js';
|
|
13
|
+
import { guessLabelFromName, suggestIfTypo } from './utils.js';
|
|
14
14
|
|
|
15
15
|
import crypto from 'crypto';
|
|
16
16
|
|
|
@@ -51,17 +51,6 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
51
51
|
validateConfig() {
|
|
52
52
|
const errors = [];
|
|
53
53
|
|
|
54
|
-
if (this.config.rootUser) {
|
|
55
|
-
if (!this.config.rootUser.username) {
|
|
56
|
-
throw new Error('rootUser.username is required');
|
|
57
|
-
}
|
|
58
|
-
if (!this.config.rootUser.password) {
|
|
59
|
-
throw new Error('rootUser.password is required');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
console.log('\n ☝️☝️☝️ [INSECURE ALERT] config.rootUser is set, please create a new user to login in backoffice and remove config.rootUser from config ASAP when you are in production\n');
|
|
63
|
-
}
|
|
64
|
-
|
|
65
54
|
if (!this.config.customization.customComponentsDir) {
|
|
66
55
|
this.config.customization.customComponentsDir = './custom';
|
|
67
56
|
}
|
|
@@ -74,8 +63,13 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
74
63
|
}
|
|
75
64
|
|
|
76
65
|
if (this.config.auth) {
|
|
77
|
-
|
|
78
|
-
|
|
66
|
+
// TODO: remove in future releases
|
|
67
|
+
if (!this.config.auth.usersResourceId && this.config.auth.resourceId) {
|
|
68
|
+
this.config.auth.usersResourceId = this.config.auth.resourceId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!this.config.auth.usersResourceId) {
|
|
72
|
+
throw new Error('No config.auth.usersResourceId defined');
|
|
79
73
|
}
|
|
80
74
|
if (!this.config.auth.passwordHashField) {
|
|
81
75
|
throw new Error('No config.auth.passwordHashField defined');
|
|
@@ -86,9 +80,10 @@ export default class ConfigValidator implements IConfigValidator {
|
|
|
86
80
|
if (this.config.auth.loginBackgroundImage) {
|
|
87
81
|
errors.push(...this.checkCustomFileExists(this.config.auth.loginBackgroundImage));
|
|
88
82
|
}
|
|
89
|
-
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.
|
|
83
|
+
const userResource = this.config.resources.find((res) => res.resourceId === this.config.auth.usersResourceId);
|
|
90
84
|
if (!userResource) {
|
|
91
|
-
|
|
85
|
+
const similar = suggestIfTypo(this.config.resources.map((res) => res.resourceId || res.table), this.config.auth.usersResourceId);
|
|
86
|
+
throw new Error(`Resource with id "${this.config.auth.usersResourceId}" not found. ${similar ? `Did you mean "${similar}"?` : ''}`);
|
|
92
87
|
}
|
|
93
88
|
|
|
94
89
|
if (!this.config.auth.beforeLoginConfirmation) {
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { IAdminForthFilter, IAdminForthSort, IOperationalResource, IAdminForthDataSourceConnectorBase, AdminForthResource, IAdminForth } from '../types/AdminForthConfig.js';
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function filtersIfFilter(filter: IAdminForthFilter | IAdminForthFilter[]): IAdminForthFilter[] {
|
|
5
|
+
return (Array.isArray(filter) ? filter : [filter]) as IAdminForthFilter[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function sortsIfSort(sort: IAdminForthSort | IAdminForthSort[]): IAdminForthSort[] {
|
|
9
|
+
return (Array.isArray(sort) ? sort : [sort]) as IAdminForthSort[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export default class OperationalResource implements IOperationalResource {
|
|
13
|
+
dataConnector: IAdminForthDataSourceConnectorBase;
|
|
14
|
+
resourceConfig: AdminForthResource;
|
|
15
|
+
|
|
16
|
+
constructor(dataConnector: IAdminForthDataSourceConnectorBase, resourceConfig: AdminForthResource) {
|
|
17
|
+
this.dataConnector = dataConnector;
|
|
18
|
+
this.resourceConfig = resourceConfig;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async get(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<any | null> {
|
|
22
|
+
return (
|
|
23
|
+
await this.dataConnector.getData({
|
|
24
|
+
resource: this.resourceConfig,
|
|
25
|
+
filters: filtersIfFilter(filter),
|
|
26
|
+
limit: 1,
|
|
27
|
+
offset: 0,
|
|
28
|
+
sort: [],
|
|
29
|
+
})
|
|
30
|
+
).data[0] || null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async list(
|
|
34
|
+
filter: IAdminForthFilter | IAdminForthFilter[],
|
|
35
|
+
limit: number,
|
|
36
|
+
offset: number,
|
|
37
|
+
sort: IAdminForthSort | IAdminForthSort[]
|
|
38
|
+
): Promise<any[]> {
|
|
39
|
+
const { data } = await this.dataConnector.getData({
|
|
40
|
+
resource: this.resourceConfig,
|
|
41
|
+
filters: filtersIfFilter(filter),
|
|
42
|
+
limit,
|
|
43
|
+
offset,
|
|
44
|
+
sort: sortsIfSort(sort),
|
|
45
|
+
getTotals: false,
|
|
46
|
+
});
|
|
47
|
+
return data;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async count(filter: IAdminForthFilter | IAdminForthFilter[]): Promise<number> {
|
|
51
|
+
return await this.dataConnector.getCount({
|
|
52
|
+
resource: this.resourceConfig,
|
|
53
|
+
filters: filtersIfFilter(filter),
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async create(record: any): Promise<any> {
|
|
58
|
+
return await this.dataConnector.createRecord({ resource: this.resourceConfig, record, adminUser: null });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async update(primaryKey: any, record: any): Promise<any> {
|
|
62
|
+
return await this.dataConnector.updateRecord({
|
|
63
|
+
resource: this.resourceConfig,
|
|
64
|
+
recordId: primaryKey,
|
|
65
|
+
newValues: record
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async delete(primaryKey: any): Promise<boolean> {
|
|
70
|
+
return await this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
}
|
package/modules/restApi.ts
CHANGED
|
@@ -67,73 +67,64 @@ export default class AdminForthRestAPI {
|
|
|
67
67
|
let adminUser: AdminUser;
|
|
68
68
|
let toReturn: { ok: boolean, redirectTo?: string, allowedLogin:boolean } = { ok: true, allowedLogin:true};
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
if (this.adminforth.config.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
)
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation as (BeforeLoginConfirmationFunction[] | undefined);
|
|
119
|
-
if (beforeLoginConfirmation?.length){
|
|
120
|
-
for (const hook of beforeLoginConfirmation) {
|
|
121
|
-
const resp = await hook({ adminUser, response });
|
|
122
|
-
|
|
123
|
-
if (resp?.body?.redirectTo) {
|
|
124
|
-
toReturn = {ok:resp.ok, redirectTo:resp?.body?.redirectTo, allowedLogin:resp?.body?.allowedLogin};
|
|
125
|
-
break;
|
|
126
|
-
}
|
|
70
|
+
// get resource from db
|
|
71
|
+
if (!this.adminforth.config.auth) {
|
|
72
|
+
throw new Error('No config.auth defined we need it to find user, please follow the docs');
|
|
73
|
+
}
|
|
74
|
+
const userResource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.usersResourceId);
|
|
75
|
+
// if there is no passwordHashField, in columns, add it, with backendOnly and showIn: []
|
|
76
|
+
if (!userResource.dataSourceColumns.find((col) => col.name === this.adminforth.config.auth.passwordHashField)) {
|
|
77
|
+
userResource.dataSourceColumns.push({
|
|
78
|
+
name: this.adminforth.config.auth.passwordHashField,
|
|
79
|
+
backendOnly: true,
|
|
80
|
+
showIn: [],
|
|
81
|
+
type: AdminForthDataTypes.STRING,
|
|
82
|
+
});
|
|
83
|
+
console.log('Adding passwordHashField to userResource', userResource)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const userRecord = (
|
|
87
|
+
await this.adminforth.connectors[userResource.dataSource].getData({
|
|
88
|
+
resource: userResource,
|
|
89
|
+
filters: [
|
|
90
|
+
{ field: this.adminforth.config.auth.usernameField, operator: AdminForthFilterOperators.EQ, value: username },
|
|
91
|
+
],
|
|
92
|
+
limit: 1,
|
|
93
|
+
offset: 0,
|
|
94
|
+
sort: [],
|
|
95
|
+
})
|
|
96
|
+
).data?.[0];
|
|
97
|
+
|
|
98
|
+
if (!userRecord) {
|
|
99
|
+
return { error: 'User not found' };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const passwordHash = userRecord[this.adminforth.config.auth.passwordHashField];
|
|
103
|
+
const valid = await AdminForthAuth.verifyPassword(password, passwordHash);
|
|
104
|
+
if (valid) {
|
|
105
|
+
adminUser = {
|
|
106
|
+
dbUser: userRecord,
|
|
107
|
+
pk: userRecord[userResource.columns.find((col) => col.primaryKey).name],
|
|
108
|
+
username,
|
|
109
|
+
};
|
|
110
|
+
const beforeLoginConfirmation = this.adminforth.config.auth.beforeLoginConfirmation as (BeforeLoginConfirmationFunction[] | undefined);
|
|
111
|
+
if (beforeLoginConfirmation?.length){
|
|
112
|
+
for (const hook of beforeLoginConfirmation) {
|
|
113
|
+
const resp = await hook({ adminUser, response });
|
|
114
|
+
|
|
115
|
+
if (resp?.body?.redirectTo) {
|
|
116
|
+
toReturn = {ok:resp.ok, redirectTo:resp?.body?.redirectTo, allowedLogin:resp?.body?.allowedLogin};
|
|
117
|
+
break;
|
|
127
118
|
}
|
|
128
119
|
}
|
|
129
|
-
if (toReturn.allowedLogin){
|
|
130
|
-
this.adminforth.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
|
|
131
|
-
}
|
|
132
|
-
} else {
|
|
133
|
-
return { error: INVALID_MESSAGE };
|
|
134
120
|
}
|
|
135
|
-
|
|
121
|
+
if (toReturn.allowedLogin){
|
|
122
|
+
this.adminforth.auth.setAuthCookie({ response, username, pk: userRecord[userResource.columns.find((col) => col.primaryKey).name] });
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
return { error: INVALID_MESSAGE };
|
|
136
126
|
}
|
|
127
|
+
|
|
137
128
|
|
|
138
129
|
return toReturn;
|
|
139
130
|
}
|
|
@@ -168,7 +159,7 @@ export default class AdminForthRestAPI {
|
|
|
168
159
|
throw new Error('No config.auth defined');
|
|
169
160
|
}
|
|
170
161
|
const usernameField = this.adminforth.config.auth.usernameField;
|
|
171
|
-
const resource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.
|
|
162
|
+
const resource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.usersResourceId);
|
|
172
163
|
const usernameColumn = resource.columns.find((col) => col.name === usernameField);
|
|
173
164
|
|
|
174
165
|
return {
|
|
@@ -188,14 +179,10 @@ export default class AdminForthRestAPI {
|
|
|
188
179
|
handler: async ({input, adminUser, cookies}) => {
|
|
189
180
|
let username = ''
|
|
190
181
|
let userFullName = ''
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const dbUser = adminUser.dbUser;
|
|
196
|
-
username = dbUser[this.adminforth.config.auth.usernameField];
|
|
197
|
-
userFullName =dbUser[this.adminforth.config.auth.userFullNameField];
|
|
198
|
-
}
|
|
182
|
+
|
|
183
|
+
const dbUser = adminUser.dbUser;
|
|
184
|
+
username = dbUser[this.adminforth.config.auth.usernameField];
|
|
185
|
+
userFullName =dbUser[this.adminforth.config.auth.userFullNameField];
|
|
199
186
|
|
|
200
187
|
const userData = {
|
|
201
188
|
[this.adminforth.config.auth.usernameField]: username,
|
package/modules/utils.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from 'path';
|
|
2
2
|
import { fileURLToPath } from 'url';
|
|
3
3
|
import fs from 'fs';
|
|
4
|
+
import Fuse from 'fuse.js';
|
|
4
5
|
// @ts-ignore-next-line
|
|
5
6
|
|
|
6
7
|
|
|
@@ -335,3 +336,24 @@ export function inverseRGBA(rgba) {
|
|
|
335
336
|
return brightness > 128 ? 'rgba(0,0,0,1)' : 'rgba(255,255,255,1)';
|
|
336
337
|
}
|
|
337
338
|
|
|
339
|
+
|
|
340
|
+
export function suggestIfTypo(names: string[], name: string): string {
|
|
341
|
+
console.log('names', names)
|
|
342
|
+
console.log('name', name)
|
|
343
|
+
if (!name) {
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
const options = {
|
|
347
|
+
includeScore: true, // Includes score in the results to see how close matches are
|
|
348
|
+
threshold: 0.3, // Defines the fuzziness (lower values mean stricter matches)
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
const fuse = new Fuse(names.filter(
|
|
352
|
+
(n) => !!n
|
|
353
|
+
), options);
|
|
354
|
+
// Search for a resource
|
|
355
|
+
const result = fuse.search(name);
|
|
356
|
+
if (result.length > 0) {
|
|
357
|
+
return result[0].item;
|
|
358
|
+
}
|
|
359
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adminforth",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"description": "OpenSource Vue3 powered forth-generation admin panel",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"express": "^4.19.2",
|
|
24
24
|
"filewatcher": "^3.0.1",
|
|
25
25
|
"fs-extra": "^11.2.0",
|
|
26
|
+
"fuse.js": "^7.0.0",
|
|
26
27
|
"jsonwebtoken": "^9.0.2",
|
|
27
28
|
"mongodb": "6.6",
|
|
28
29
|
"node-fetch": "^3.3.2",
|