adminforth 1.2.99 → 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 +20 -3
- package/dataConnectors/clickhouse.ts +1 -0
- package/dist/auth.js +6 -6
- package/dist/dataConnectors/baseConnector.js +16 -3
- package/dist/dataConnectors/clickhouse.js +1 -0
- package/dist/index.js +34 -16
- package/dist/modules/configValidator.js +10 -14
- package/dist/modules/operationalResource.js +6 -22
- package/dist/modules/restApi.js +75 -90
- package/dist/modules/utils.js +18 -0
- package/index.ts +37 -17
- package/modules/configValidator.ts +11 -16
- package/modules/operationalResource.ts +14 -38
- package/modules/restApi.ts +59 -72
- package/modules/utils.ts +22 -0
- package/package.json +2 -1
- package/types/AdminForthConfig.ts +27 -29
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",
|
|
@@ -51,16 +51,18 @@ export interface IHttpServer {
|
|
|
51
51
|
|
|
52
52
|
export type AdminUser = {
|
|
53
53
|
/**
|
|
54
|
-
* primaryKey field value of user in table which is defined by {@link AdminForthConfig.auth.
|
|
55
|
-
* or null if it is user logged in as {@link AdminForthConfig.rootUser}
|
|
54
|
+
* primaryKey field value of user in table which is defined by {@link AdminForthConfig.auth.usersResourceId}
|
|
56
55
|
*/
|
|
57
56
|
pk: string | null,
|
|
58
57
|
|
|
59
58
|
/**
|
|
60
|
-
* Username which
|
|
59
|
+
* Username which taken from {@link AdminForthConfig.auth.usernameField} field in user resource {@link AdminForthConfig.auth.usersResourceId}
|
|
61
60
|
*/
|
|
62
61
|
username: string,
|
|
63
|
-
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* User record fetched from database, from resource defined in {@link AdminForthConfig.auth.usersResourceId}
|
|
65
|
+
*/
|
|
64
66
|
dbUser: any,
|
|
65
67
|
}
|
|
66
68
|
|
|
@@ -220,7 +222,11 @@ export interface IAdminForthDataSourceConnectorBase extends IAdminForthDataSourc
|
|
|
220
222
|
|
|
221
223
|
getRecordByPrimaryKey(resource: AdminForthResource, recordId: string): Promise<any>;
|
|
222
224
|
|
|
223
|
-
createRecord({ resource, record }: {
|
|
225
|
+
createRecord({ resource, record, adminUser }: {
|
|
226
|
+
resource: AdminForthResource,
|
|
227
|
+
record: any
|
|
228
|
+
adminUser: AdminUser
|
|
229
|
+
}): Promise<void>;
|
|
224
230
|
|
|
225
231
|
getMinMaxForColumns({ resource, columns }: { resource: AdminForthResource, columns: AdminForthResourceColumn[] }): Promise<{ [key: string]: { min: any, max: any } }>;
|
|
226
232
|
}
|
|
@@ -744,7 +750,7 @@ export type AdminForthBulkAction = {
|
|
|
744
750
|
*
|
|
745
751
|
* ```ts
|
|
746
752
|
* allowed: async ({ resource, adminUser, selectedIds }) => {
|
|
747
|
-
* if (adminUser.
|
|
753
|
+
* if (adminUser.dbUser.role !== 'superadmin') {
|
|
748
754
|
* return false;
|
|
749
755
|
* }
|
|
750
756
|
* return true;
|
|
@@ -786,7 +792,7 @@ export type AdminForthBulkAction = {
|
|
|
786
792
|
* ],
|
|
787
793
|
* allowedActions: \{
|
|
788
794
|
* edit: (\{ resource, adminUser, recordIds \}) => \{
|
|
789
|
-
* return adminUser.
|
|
795
|
+
* return adminUser.dbUser.role === 'superadmin';
|
|
790
796
|
* \}
|
|
791
797
|
* \}
|
|
792
798
|
* \}
|
|
@@ -979,8 +985,8 @@ export type AdminForthResource = {
|
|
|
979
985
|
* ```ts
|
|
980
986
|
* allowedActions: {
|
|
981
987
|
* create: ({ resource, adminUser }) => {
|
|
982
|
-
* // Allow only superadmin
|
|
983
|
-
* return adminUser.
|
|
988
|
+
* // Allow only superadmin to create records
|
|
989
|
+
* return adminUser.dbUser.role === 'superadmin';
|
|
984
990
|
* },
|
|
985
991
|
* delete: false, // disable delete action for all users
|
|
986
992
|
* }
|
|
@@ -1085,33 +1091,21 @@ export type AdminForthDataSource = {
|
|
|
1085
1091
|
*/
|
|
1086
1092
|
export type AdminForthConfig = {
|
|
1087
1093
|
|
|
1088
|
-
/**
|
|
1089
|
-
* Root user should be used to login to the admin panel first time.
|
|
1090
|
-
* Then you should create User for yourself using AdminForth (so it will be persisted in DB),
|
|
1091
|
-
* and then disable this option as it is less secure
|
|
1092
|
-
*/
|
|
1093
|
-
rootUser?: {
|
|
1094
|
-
/**
|
|
1095
|
-
* Username for root user
|
|
1096
|
-
*/
|
|
1097
|
-
username: string,
|
|
1098
|
-
|
|
1099
|
-
/**
|
|
1100
|
-
* Password for root user
|
|
1101
|
-
*/
|
|
1102
|
-
password: string,
|
|
1103
|
-
},
|
|
1104
|
-
|
|
1105
1094
|
/**
|
|
1106
1095
|
* Authorization module configuration
|
|
1107
1096
|
*/
|
|
1108
1097
|
auth?: {
|
|
1109
1098
|
/**
|
|
1110
|
-
* Resource ID for user
|
|
1099
|
+
* Resource ID for resource which stores user table.
|
|
1111
1100
|
* Resource is a table in database where users will be stored and fetched from. Resources and their ids are defined in resources section of the config.
|
|
1112
1101
|
* In other words this setting is a reference to a table in database where users will be fetched from on login.
|
|
1113
1102
|
*/
|
|
1114
|
-
|
|
1103
|
+
usersResourceId?: string,
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Legacy field left for backward compatibility. Use usersResourceId instead.
|
|
1107
|
+
*/
|
|
1108
|
+
resourceId?: string,
|
|
1115
1109
|
|
|
1116
1110
|
/**
|
|
1117
1111
|
* Field name (column name) in user resource which will be used as username for searching user in database during login.
|
|
@@ -1322,6 +1316,10 @@ export type AdminForthConfig = {
|
|
|
1322
1316
|
baseUrl?: string,
|
|
1323
1317
|
|
|
1324
1318
|
|
|
1319
|
+
/**
|
|
1320
|
+
* Whether to show delete confirmation dialog before deleting record.
|
|
1321
|
+
* By default it is true.
|
|
1322
|
+
*/
|
|
1325
1323
|
deleteConfirmation?: boolean,
|
|
1326
1324
|
|
|
1327
1325
|
|
|
@@ -1375,7 +1373,7 @@ export class Sorts {
|
|
|
1375
1373
|
}
|
|
1376
1374
|
|
|
1377
1375
|
export interface IOperationalResource {
|
|
1378
|
-
get: (filter: IAdminForthFilter | IAdminForthFilter[]) => Promise<any
|
|
1376
|
+
get: (filter: IAdminForthFilter | IAdminForthFilter[]) => Promise<any | null>;
|
|
1379
1377
|
|
|
1380
1378
|
list: (filter: IAdminForthFilter | IAdminForthFilter[], limit: number, offset: number, sort: IAdminForthSort | IAdminForthSort[]) => Promise<any[]>;
|
|
1381
1379
|
|