Skip to content

Commit 45dcfcd

Browse files
authored
feat(users): enforce profile field and metadata safety limits (#130)
* feat(users): enforce profile field and metadata safety limits * Add Migrations * Fix Tests
1 parent 40278e6 commit 45dcfcd

29 files changed

Lines changed: 3088 additions & 54 deletions

File tree

‎apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/20261031185511_AddProfileFieldLimits.Designer.cs‎

Lines changed: 742 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
using Microsoft.EntityFrameworkCore.Migrations;
2+
3+
#nullable disable
4+
5+
namespace CodeBeam.UltimateAuth.IdentityLab.Migrations
6+
{
7+
/// <inheritdoc />
8+
public partial class AddProfileFieldLimits : Migration
9+
{
10+
/// <inheritdoc />
11+
protected override void Up(MigrationBuilder migrationBuilder)
12+
{
13+
migrationBuilder.AlterColumn<string>(
14+
name: "TimeZone",
15+
table: "UAuth_UserProfiles",
16+
type: "character varying(128)",
17+
maxLength: 128,
18+
nullable: true,
19+
oldClrType: typeof(string),
20+
oldType: "text",
21+
oldNullable: true);
22+
23+
migrationBuilder.AlterColumn<string>(
24+
name: "LastName",
25+
table: "UAuth_UserProfiles",
26+
type: "character varying(128)",
27+
maxLength: 128,
28+
nullable: true,
29+
oldClrType: typeof(string),
30+
oldType: "text",
31+
oldNullable: true);
32+
33+
migrationBuilder.AlterColumn<string>(
34+
name: "Language",
35+
table: "UAuth_UserProfiles",
36+
type: "character varying(64)",
37+
maxLength: 64,
38+
nullable: true,
39+
oldClrType: typeof(string),
40+
oldType: "text",
41+
oldNullable: true);
42+
43+
migrationBuilder.AlterColumn<string>(
44+
name: "Gender",
45+
table: "UAuth_UserProfiles",
46+
type: "character varying(128)",
47+
maxLength: 128,
48+
nullable: true,
49+
oldClrType: typeof(string),
50+
oldType: "text",
51+
oldNullable: true);
52+
53+
migrationBuilder.AlterColumn<string>(
54+
name: "FirstName",
55+
table: "UAuth_UserProfiles",
56+
type: "character varying(128)",
57+
maxLength: 128,
58+
nullable: true,
59+
oldClrType: typeof(string),
60+
oldType: "text",
61+
oldNullable: true);
62+
63+
migrationBuilder.AlterColumn<string>(
64+
name: "DisplayName",
65+
table: "UAuth_UserProfiles",
66+
type: "character varying(256)",
67+
maxLength: 256,
68+
nullable: true,
69+
oldClrType: typeof(string),
70+
oldType: "text",
71+
oldNullable: true);
72+
73+
migrationBuilder.AlterColumn<string>(
74+
name: "Culture",
75+
table: "UAuth_UserProfiles",
76+
type: "character varying(64)",
77+
maxLength: 64,
78+
nullable: true,
79+
oldClrType: typeof(string),
80+
oldType: "text",
81+
oldNullable: true);
82+
83+
migrationBuilder.AlterColumn<string>(
84+
name: "Bio",
85+
table: "UAuth_UserProfiles",
86+
type: "character varying(4096)",
87+
maxLength: 4096,
88+
nullable: true,
89+
oldClrType: typeof(string),
90+
oldType: "text",
91+
oldNullable: true);
92+
}
93+
94+
/// <inheritdoc />
95+
protected override void Down(MigrationBuilder migrationBuilder)
96+
{
97+
migrationBuilder.AlterColumn<string>(
98+
name: "TimeZone",
99+
table: "UAuth_UserProfiles",
100+
type: "text",
101+
nullable: true,
102+
oldClrType: typeof(string),
103+
oldType: "character varying(128)",
104+
oldMaxLength: 128,
105+
oldNullable: true);
106+
107+
migrationBuilder.AlterColumn<string>(
108+
name: "LastName",
109+
table: "UAuth_UserProfiles",
110+
type: "text",
111+
nullable: true,
112+
oldClrType: typeof(string),
113+
oldType: "character varying(128)",
114+
oldMaxLength: 128,
115+
oldNullable: true);
116+
117+
migrationBuilder.AlterColumn<string>(
118+
name: "Language",
119+
table: "UAuth_UserProfiles",
120+
type: "text",
121+
nullable: true,
122+
oldClrType: typeof(string),
123+
oldType: "character varying(64)",
124+
oldMaxLength: 64,
125+
oldNullable: true);
126+
127+
migrationBuilder.AlterColumn<string>(
128+
name: "Gender",
129+
table: "UAuth_UserProfiles",
130+
type: "text",
131+
nullable: true,
132+
oldClrType: typeof(string),
133+
oldType: "character varying(128)",
134+
oldMaxLength: 128,
135+
oldNullable: true);
136+
137+
migrationBuilder.AlterColumn<string>(
138+
name: "FirstName",
139+
table: "UAuth_UserProfiles",
140+
type: "text",
141+
nullable: true,
142+
oldClrType: typeof(string),
143+
oldType: "character varying(128)",
144+
oldMaxLength: 128,
145+
oldNullable: true);
146+
147+
migrationBuilder.AlterColumn<string>(
148+
name: "DisplayName",
149+
table: "UAuth_UserProfiles",
150+
type: "text",
151+
nullable: true,
152+
oldClrType: typeof(string),
153+
oldType: "character varying(256)",
154+
oldMaxLength: 256,
155+
oldNullable: true);
156+
157+
migrationBuilder.AlterColumn<string>(
158+
name: "Culture",
159+
table: "UAuth_UserProfiles",
160+
type: "text",
161+
nullable: true,
162+
oldClrType: typeof(string),
163+
oldType: "character varying(64)",
164+
oldMaxLength: 64,
165+
oldNullable: true);
166+
167+
migrationBuilder.AlterColumn<string>(
168+
name: "Bio",
169+
table: "UAuth_UserProfiles",
170+
type: "text",
171+
nullable: true,
172+
oldClrType: typeof(string),
173+
oldType: "character varying(4096)",
174+
oldMaxLength: 4096,
175+
oldNullable: true);
176+
}
177+
}
178+
}

‎apps/identity-lab/CodeBeam.UltimateAuth.IdentityLab/Migrations/UAuthDbContextModelSnapshot.cs‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -641,7 +641,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
641641
.HasColumnType("uuid");
642642

643643
b.Property<string>("Bio")
644-
.HasColumnType("text");
644+
.HasMaxLength(4096)
645+
.HasColumnType("character varying(4096)");
645646

646647
b.Property<DateOnly?>("BirthDate")
647648
.HasColumnType("date");
@@ -650,25 +651,31 @@ protected override void BuildModel(ModelBuilder modelBuilder)
650651
.HasColumnType("timestamp with time zone");
651652

652653
b.Property<string>("Culture")
653-
.HasColumnType("text");
654+
.HasMaxLength(64)
655+
.HasColumnType("character varying(64)");
654656

655657
b.Property<DateTime?>("DeletedAt")
656658
.HasColumnType("timestamp with time zone");
657659

658660
b.Property<string>("DisplayName")
659-
.HasColumnType("text");
661+
.HasMaxLength(256)
662+
.HasColumnType("character varying(256)");
660663

661664
b.Property<string>("FirstName")
662-
.HasColumnType("text");
665+
.HasMaxLength(128)
666+
.HasColumnType("character varying(128)");
663667

664668
b.Property<string>("Gender")
665-
.HasColumnType("text");
669+
.HasMaxLength(128)
670+
.HasColumnType("character varying(128)");
666671

667672
b.Property<string>("Language")
668-
.HasColumnType("text");
673+
.HasMaxLength(64)
674+
.HasColumnType("character varying(64)");
669675

670676
b.Property<string>("LastName")
671-
.HasColumnType("text");
677+
.HasMaxLength(128)
678+
.HasColumnType("character varying(128)");
672679

673680
b.Property<string>("Metadata")
674681
.HasColumnType("text");
@@ -684,7 +691,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
684691
.HasColumnType("character varying(128)");
685692

686693
b.Property<string>("TimeZone")
687-
.HasColumnType("text");
694+
.HasMaxLength(128)
695+
.HasColumnType("character varying(128)");
688696

689697
b.Property<DateTime?>("UpdatedAt")
690698
.HasColumnType("timestamp with time zone");

‎docs/content/plugin-domains/authorization.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Authorization
3-
order: 4
3+
order: 5
44
group: plugin-domains
55
---
66

‎docs/content/plugin-domains/credentials.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Credentials
3-
order: 3
3+
order: 4
44
group: plugin-domains
55
---
66

‎docs/content/plugin-domains/policies.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
title: Policies & Access Control
3-
order: 5
3+
order: 6
44
group: plugin-domains
55
---
66

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
---
2+
title: Profile Validation & Limits
3+
order: 3
4+
group: plugin-domains
5+
---
6+
7+
# Profile Validation & Limits
8+
9+
UltimateAuth enforces framework-level safety limits on standard user profile fields and metadata.
10+
11+
These limits protect applications from excessively large profile values, unnecessary storage consumption, and resource exhaustion.
12+
13+
They apply consistently to the reference Users domain, regardless of whether InMemory or Entity Framework Core persistence is used.
14+
15+
## Profile Field Limits
16+
17+
The following maximum lengths are enforced:
18+
19+
| Field | Maximum characters |
20+
|---|---:|
21+
| FirstName | 128 |
22+
| LastName | 128 |
23+
| DisplayName | 256 |
24+
| Gender | 128 |
25+
| Bio | 4,096 |
26+
| Language | 64 |
27+
| TimeZone | 128 |
28+
| Culture | 64 |
29+
30+
All fields are optional unless an application introduces additional requirements.
31+
32+
Limits are defined centrally in `UserProfileLimits` and cannot be increased through application configuration.
33+
34+
## Metadata Limits
35+
36+
User profiles support optional key-value metadata.
37+
38+
To prevent unbounded metadata storage, UltimateAuth enforces the following limits:
39+
40+
| Constraint | Maximum |
41+
|---|---:|
42+
| Metadata entries | 32 |
43+
| Key length | 128 characters |
44+
| Value length | 2,048 characters |
45+
| Total metadata size | 8,192 UTF-8 bytes |
46+
47+
The total size includes metadata keys and values.
48+
49+
These limits apply to the complete metadata collection, not individually to each entry.
50+
51+
## Validation Enforcement
52+
53+
Profile safety limits are enforced during:
54+
55+
- User creation.
56+
- Additional profile creation.
57+
- Profile updates.
58+
- Profile cloning.
59+
60+
Validation occurs before invalid data is persisted.
61+
62+
Framework-level safety validation is independent of `IUserProfileValidator`, ensuring that replacing the application validator cannot bypass these limits.
63+
64+
The same restrictions apply to both self-service and administrative operations.
65+
66+
## Custom Validation
67+
68+
Applications can implement `IUserProfileValidator` to introduce additional validation rules.
69+
70+
For example, an application may require a display name, restrict accepted languages, or enforce a shorter biography.
71+
72+
Custom validation can be stricter than the framework limits but cannot override the built-in safety boundaries.
73+
74+
The recommended approach is to use `IUserProfileValidator` for application-specific rules while relying on UltimateAuth for baseline safety validation.
75+
76+
## Validation Errors
77+
78+
When a profile exceeds the supported limits, UltimateAuth rejects the operation with a validation error.
79+
80+
Common error codes include:
81+
82+
| Error code | Meaning |
83+
|---|---|
84+
| `profile_field_too_long` | A standard profile field exceeds its maximum length. |
85+
| `profile_metadata_entries_exceeded` | The metadata collection contains too many entries. |
86+
| `profile_metadata_key_too_long` | A metadata key exceeds its maximum length. |
87+
| `profile_metadata_value_too_long` | A metadata value exceeds its maximum length. |
88+
| `profile_metadata_too_large` | The combined metadata size exceeds the allowed limit. |
89+
90+
Applications should use the error `Code` rather than relying on exception message text.
91+
92+
## Persistence Considerations
93+
94+
The reference Entity Framework Core implementation configures profile column lengths using the same framework limits.
95+
96+
Existing databases may require a migration when upgrading to a version that introduces these constraints.
97+
98+
Before applying restrictive database changes, review existing profile records for values exceeding the new limits.
99+
100+
The InMemory implementation uses the same application-level validation rules.
101+
102+
## Security Considerations
103+
104+
Profile limits are a defense-in-depth measure.
105+
106+
They do not replace HTTP request size limits, rate limiting, or application-specific validation.
107+
108+
Applications should configure these additional protections according to their deployment requirements.
109+
110+
For an overview of user identity and profile management, see [Users Domain](/docs/plugin-domains/users).

0 commit comments

Comments
 (0)