| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -311,6 +311,7 @@ export default { | |||
| 311 | 311 | required: true, | |
| 312 | 312 | isUnique: true, | |
| 313 | 313 | type: AdminForthDataTypes.STRING, | |
| 314 | + normalize: (value: string) => value.trim().toLowerCase(), | ||
| 314 | 315 | validation: [ | |
| 315 | 316 | // you can also use AdminForth.Utils.EMAIL_VALIDATOR which is alias to this object | |
| 316 | 317 | { | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -98,6 +98,62 @@ Also you can add custom rules. For example to prevent popular words: | |||
| 98 | 98 | ||
| 99 | 99 | All rules defined in password column will be also delivered to [password reset plugin](../09-Plugins/07-email-password-reset.md) if you are using it to ensure that password reset will also respect same rules. | |
| 100 | 100 | ||
| 101 | + ## Normalizing identity columns | ||
| 102 | + | ||
| 103 | + Use the optional column-level `normalize` callback to store an identity value in one canonical form. Email addresses are a common example: | ||
| 104 | + | ||
| 105 | + ```ts title="./resources/adminuser.ts" | ||
| 106 | + { | ||
| 107 | + name: 'email', | ||
| 108 | + required: true, | ||
| 109 | + isUnique: true, | ||
| 110 | + type: AdminForthDataTypes.STRING, | ||
| 111 | + normalize: (value: string) => value.trim().toLowerCase(), | ||
| 112 | + } | ||
| 113 | + ``` | ||
| 114 | + | ||
| 115 | + This is opt-in. It is especially important for the column configured as `auth.usernameField`: without it, password login compares the submitted username exactly as written. That makes login case-sensitive and lets `User@example.com` and `user@example.com` be created as separate accounts when the database treats them as distinct values. | ||
| 116 | + | ||
| 117 | + `normalize` is deliberately a write-time feature, except for the core password-login lookup: | ||
| 118 | + | ||
| 119 | + | Path | When `normalize` runs | | ||
| 120 | + | --- | --- | | ||
| 121 | + | AdminForth CRUD (`createResourceRecord`, `updateResourceRecord`) | Before validation and `beforeSave` hooks | | ||
| 122 | + | Data API (`admin.resource(...).create()` and `.update()`) | Before the record reaches the connector | | ||
| 123 | + | Core password login | On the submitted value of `auth.usernameField`, before the user lookup | | ||
| 124 | + | Reads and filters | Never — this includes `get`, `list`, `count`, search, and `Filters.EQ` | | ||
| 125 | + | ||
| 126 | + Plugins that create or update records through the Data API use the same normalization. Code that calls a data connector's `createRecord` or `updateRecord` directly bypasses it, so those callers must normalize the value themselves or use the Data API instead. | ||
| 127 | + | ||
| 128 | + ### Enabling it on an existing table | ||
| 129 | + | ||
| 130 | + `normalize` affects **new writes only**. It never rewrites stored user data automatically. Existing rows keep their current casing, so migrating them is the operator's responsibility. | ||
| 131 | + | ||
| 132 | + > Warning: if a stored email is `User@example.com`, enabling this normalizer changes a submitted `User@example.com` to `user@example.com` before login looks it up. The existing row does not become lowercase on its own, so existing identity data must be migrated. | ||
| 133 | + | ||
| 134 | + Before enabling it, check for values that would collide after normalization. For an `adminuser.email` column, run this query with your normalizer's equivalent SQL expression: | ||
| 135 | + | ||
| 136 | + ```sql | ||
| 137 | + SELECT LOWER(TRIM(email)) AS normalized_email, COUNT(*) AS account_count | ||
| 138 | + FROM adminuser | ||
| 139 | + GROUP BY LOWER(TRIM(email)) | ||
| 140 | + HAVING COUNT(*) > 1; | ||
| 141 | + ``` | ||
| 142 | + | ||
| 143 | + Resolve every reported collision before continuing. For example, `User@example.com` and `user@example.com` remain different rows until you explicitly merge, rename, or remove one of them. Enabling the callback does not make reads case-insensitive; a future normalized lookup could match both rows and choose an arbitrary account. | ||
| 144 | + | ||
| 145 | + After taking a backup and confirming the query returns no rows, add `normalize` to the resource and run a database migration such as: | ||
| 146 | + | ||
| 147 | + ```sql | ||
| 148 | + UPDATE adminuser | ||
| 149 | + SET email = LOWER(TRIM(email)) | ||
| 150 | + WHERE email <> LOWER(TRIM(email)); | ||
| 151 | + ``` | ||
| 152 | + | ||
| 153 | + Replace `adminuser`, `email`, and `LOWER(TRIM(...))` with the table, column, and database expression that match your configuration. Run this as a tested migration in a maintenance window when identity data is sensitive. The migration and the callback together ensure both existing records and future writes use the same canonical value. | ||
| 154 | + | ||
| 155 | + For the complete CRUD behavior, see [Normalize values before saving](./13-standardPagesTuning.md#normalize-values-before-saving). | ||
| 156 | + | ||
| 101 | 157 | ||
| 102 | 158 | ## Trusting client IP addresses | |
| 103 | 159 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -728,7 +728,10 @@ export interface AdminForthResourceColumnInputCommon { | |||
| 728 | 728 | name: string, | |
| 729 | 729 | ||
| 730 | 730 | /** | |
| 731 | - * Normalizes a column value before it is saved or used as the username during login. | ||
| 731 | + * Normalizes a column value before AdminForth CRUD create and update operations and, when this column | ||
| 732 | + * is configured as `auth.usernameField`, before the password-login lookup. | ||
| 733 | + * | ||
| 734 | + * Does not normalize filter values or existing stored records. | ||
| 732 | 735 | * | |
| 733 | 736 | * @example | |
| 734 | 737 | * ```ts | |
| Back | FazBrowse Home | New Git URL |
0 commit comments