Skip to content

Commit da81d2f

Browse files
committed
Merge branch 'main' of github.com:devforth/adminforth
AdminForth/1902/image AdminForth/1902/image
2 parents 8ceeb5d + bec906c commit da81d2f

8 files changed

Lines changed: 63 additions & 10 deletions

File tree

adminforth/commands/createApp/templates/adminuser.ts.hbs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default {
3636
required: true,
3737
isUnique: true,
3838
type: AdminForthDataTypes.STRING,
39+
normalize: (value: string) => value.trim().toLowerCase(),
3940
validation: [
4041
// you can also use AdminForth.Utils.EMAIL_VALIDATOR which is alias to this object
4142
{
@@ -108,4 +109,4 @@ export default {
108109
},
109110
},
110111
},
111-
} as AdminForthResourceInput;
112+
} as AdminForthResourceInput;

adminforth/index.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import AdminForthRestAPI, { interpretResource, rejectApiRawFilters } from './mod
3939
import OperationalResource from './modules/operationalResource.js';
4040
import SocketBroker from './modules/socketBroker.js';
4141
import { afLogger } from './modules/logger.js';
42+
import { normalizeRecordValues } from './modules/columnValueNormalizer.js';
4243
export { afLogger } from './modules/logger.js';
4344
export { dbLogger } from './modules/logger.js';
4445
export { logger } from './modules/logger.js';
@@ -726,6 +727,8 @@ class AdminForth implements IAdminForth {
726727
): Promise<CreateResourceRecordResult> {
727728
const { resource, record, adminUser, extra, response } = params;
728729

730+
normalizeRecordValues(resource, record);
731+
729732
const err = this.validateRecordValues(resource, record, 'create');
730733
if (err) {
731734
return { error: err };
@@ -816,6 +819,7 @@ class AdminForth implements IAdminForth {
816819
): Promise<UpdateResourceRecordResult> {
817820
const { resource, recordId, record, oldRecord, adminUser, response, extra, updates } = params;
818821
const dataToUse = updates || record;
822+
normalizeRecordValues(resource, dataToUse);
819823
const err = this.validateRecordValues(resource, dataToUse, 'edit');
820824
if (err) {
821825
return { error: err };
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { AdminForthResource, AdminForthResourceColumn } from '../types/Back.js';
2+
3+
export function normalizeColumnValue(column: AdminForthResourceColumn, value: any): any {
4+
return column.normalize ? column.normalize(value) : value;
5+
}
6+
7+
export function normalizeRecordValues(resource: AdminForthResource, record: Record<string, any>): void {
8+
for (const column of resource.columns) {
9+
if (column.name in record) {
10+
record[column.name] = normalizeColumnValue(column, record[column.name]);
11+
}
12+
}
13+
}

adminforth/modules/operationalResource.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { IAdminForthSingleFilter, IAdminForthAndOrFilter, IAdminForthSort, IOperationalResource, IAdminForthDataSourceConnectorBase, AdminForthResource, IAggregationRule, IGroupByRule } from '../types/Back.js';
22
import { AdminForthFilterOperators } from '../types/Common.js';
3+
import { normalizeRecordValues } from './columnValueNormalizer.js';
34

45
function sortsIfSort(sort: IAdminForthSort | IAdminForthSort[]): IAdminForthSort[] {
56
return (Array.isArray(sort) ? sort : [sort]) as IAdminForthSort[];
@@ -82,9 +83,11 @@ export default class OperationalResource implements IOperationalResource {
8283
}
8384

8485
async create(recordValues: any): Promise<{ ok: boolean; createdRecord: any; error?: string; }> {
86+
const normalizedRecord = { ...recordValues };
87+
normalizeRecordValues(this.resourceConfig, normalizedRecord);
8588
const { ok, createdRecord, error } = await this.dataConnector.createRecord({
8689
resource: this.resourceConfig,
87-
record: recordValues,
90+
record: normalizedRecord,
8891
adminUser: null
8992
});
9093
return { ok, createdRecord, error };
@@ -95,15 +98,18 @@ export default class OperationalResource implements IOperationalResource {
9598
return { ok: true };
9699
}
97100

101+
const normalizedRecord = { ...record };
102+
normalizeRecordValues(this.resourceConfig, normalizedRecord);
103+
98104
return await this.dataConnector.updateRecord({
99105
resource: this.resourceConfig,
100106
recordId: primaryKey,
101-
newValues: record
107+
newValues: normalizedRecord
102108
});
103109
}
104110

105111
async delete(primaryKey: any): Promise<boolean> {
106112
return await this.dataConnector.deleteRecord({ resource: this.resourceConfig, recordId: primaryKey });
107113
}
108114

109-
}
115+
}

adminforth/modules/restApi.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { ActionCheckSource, AdminForthActionFront, AdminForthConfigMenuItem, Adm
3131
GetConfigResponse,
3232
ShowInResolved} from "../types/Common.js";
3333
import { filtersTools } from "../modules/filtersTools.js";
34-
import is_ip_private from 'private-ip'
34+
import { normalizeColumnValue } from './columnValueNormalizer.js';
3535

3636

3737
async function resolveBoolOrFn(
@@ -760,6 +760,8 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
760760
throw new Error('No config.auth defined we need it to find user, please follow the docs');
761761
}
762762
const userResource = this.adminforth.config.resources.find((res) => res.resourceId === this.adminforth.config.auth.usersResourceId);
763+
const usernameColumn = userResource.columns.find((col) => col.name === this.adminforth.config.auth.usernameField);
764+
const normalizedUsername = normalizeColumnValue(usernameColumn, username);
763765
// if there is no passwordHashField, in columns, add it, with backendOnly and showIn: []
764766
if (!userResource.dataSourceColumns.find((col) => col.name === this.adminforth.config.auth.passwordHashField)) {
765767
userResource.dataSourceColumns.push({
@@ -775,7 +777,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
775777
await this.adminforth.connectors[userResource.dataSource].getData({
776778
resource: userResource,
777779
filters: { operator: AdminForthFilterOperators.AND, subFilters: [
778-
{ field: this.adminforth.config.auth.usernameField, operator: AdminForthFilterOperators.EQ, value: username },
780+
{ field: this.adminforth.config.auth.usernameField, operator: AdminForthFilterOperators.EQ, value: normalizedUsername },
779781
]},
780782
limit: 1,
781783
offset: 0,
@@ -795,7 +797,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
795797
adminUser = {
796798
dbUser: userRecord,
797799
pk: userRecord[userResource.columns.find((col) => col.primaryKey).name],
798-
username,
800+
username: normalizedUsername,
799801
};
800802

801803
const expireInDuration = rememberMe
@@ -811,7 +813,7 @@ export default class AdminForthRestAPI implements IAdminForthRestAPI {
811813
this.adminforth.auth.setAuthCookie({
812814
expireInDuration,
813815
response,
814-
username,
816+
username: normalizedUsername,
815817
pk: userRecord[userResource.columns.find((col) => col.primaryKey).name]
816818
});
817819
}

adminforth/types/Common.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,16 @@ export interface AdminForthResourceColumnInputCommon {
725725
*/
726726
name: string,
727727

728+
/**
729+
* Normalizes a column value before it is saved or used as the username during login.
730+
*
731+
* @example
732+
* ```ts
733+
* normalize: (value: string) => value.trim().toLowerCase(),
734+
* ```
735+
*/
736+
normalize?: (value: any) => any,
737+
728738
/**
729739
* How column can be labled in the admin panel.
730740
* Use it for renaming columns. Defaulted to column name with Uppercased first letter.

tests/application/resources/adminuser.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import usersResource from "../../../dev-demo/resources/adminuser.js";
22

33
export default {
44
...usersResource,
5+
columns: usersResource.columns.map((column) => column.name === 'email'
6+
? { ...column, normalize: (value: string) => value.trim().toLowerCase() }
7+
: column),
58
plugins: [
69
...usersResource.plugins?.filter((p) => ![
710
'AdminForthAgentPlugin',
811
'TwoFactorsAuthPlugin',
912
'DashboardPlugin',
1013
].includes(p.className)) || [],
1114
],
12-
}
15+
}

tests/jest_tests/CRUD_sqlite.test.ts

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,20 @@ afterAll(async () => {
77
await closeApplication();
88
});
99

10+
describe('POST /login', () => {
11+
it('normalizes the username using the configured column normalizer', async () => {
12+
const res = await agent
13+
.post('/adminapi/v1/login')
14+
.send({
15+
username: ' ADMINFORTH ',
16+
password: 'adminforth',
17+
});
18+
19+
expect(res.status).toEqual(200);
20+
expect(res.body.error).toBeUndefined();
21+
});
22+
});
23+
1024
describe('POST /create_record', () => {
1125
const requestBody: any ={
1226
"resourceId": "cars_sl",
@@ -378,7 +392,7 @@ describe('POST /update_record', () => {
378392
body_type: "sedan",
379393
});
380394
});
381-
395+
382396
it('should throw error, that resource is not found', async () => {
383397
const res = await agent
384398
.set('Cookie', authCookie)

0 commit comments

Comments
 (0)