Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
using Microsoft.EntityFrameworkCore.Migrations;

#nullable disable

namespace CodeBeam.UltimateAuth.IdentityLab.Migrations
{
/// <inheritdoc />
public partial class AddProfileFieldLimits : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "TimeZone",
table: "UAuth_UserProfiles",
type: "character varying(128)",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "LastName",
table: "UAuth_UserProfiles",
type: "character varying(128)",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "Language",
table: "UAuth_UserProfiles",
type: "character varying(64)",
maxLength: 64,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "Gender",
table: "UAuth_UserProfiles",
type: "character varying(128)",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "FirstName",
table: "UAuth_UserProfiles",
type: "character varying(128)",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "DisplayName",
table: "UAuth_UserProfiles",
type: "character varying(256)",
maxLength: 256,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "Culture",
table: "UAuth_UserProfiles",
type: "character varying(64)",
maxLength: 64,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "Bio",
table: "UAuth_UserProfiles",
type: "character varying(4096)",
maxLength: 4096,
nullable: true,
oldClrType: typeof(string),
oldType: "text",
oldNullable: true);
}

/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "TimeZone",
table: "UAuth_UserProfiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128,
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "LastName",
table: "UAuth_UserProfiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128,
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "Language",
table: "UAuth_UserProfiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "Gender",
table: "UAuth_UserProfiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128,
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "FirstName",
table: "UAuth_UserProfiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128,
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "DisplayName",
table: "UAuth_UserProfiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(256)",
oldMaxLength: 256,
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "Culture",
table: "UAuth_UserProfiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(64)",
oldMaxLength: 64,
oldNullable: true);

migrationBuilder.AlterColumn<string>(
name: "Bio",
table: "UAuth_UserProfiles",
type: "text",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(4096)",
oldMaxLength: 4096,
oldNullable: true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -641,7 +641,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("uuid");

b.Property<string>("Bio")
.HasColumnType("text");
.HasMaxLength(4096)
.HasColumnType("character varying(4096)");

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

b.Property<string>("Culture")
.HasColumnType("text");
.HasMaxLength(64)
.HasColumnType("character varying(64)");

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

b.Property<string>("DisplayName")
.HasColumnType("text");
.HasMaxLength(256)
.HasColumnType("character varying(256)");

b.Property<string>("FirstName")
.HasColumnType("text");
.HasMaxLength(128)
.HasColumnType("character varying(128)");

b.Property<string>("Gender")
.HasColumnType("text");
.HasMaxLength(128)
.HasColumnType("character varying(128)");

b.Property<string>("Language")
.HasColumnType("text");
.HasMaxLength(64)
.HasColumnType("character varying(64)");

b.Property<string>("LastName")
.HasColumnType("text");
.HasMaxLength(128)
.HasColumnType("character varying(128)");

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

b.Property<string>("TimeZone")
.HasColumnType("text");
.HasMaxLength(128)
.HasColumnType("character varying(128)");

b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
Expand Down
2 changes: 1 addition & 1 deletion docs/content/plugin-domains/authorization.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Authorization
order: 4
order: 5
group: plugin-domains
---

Expand Down
2 changes: 1 addition & 1 deletion docs/content/plugin-domains/credentials.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Credentials
order: 3
order: 4
group: plugin-domains
---

Expand Down
2 changes: 1 addition & 1 deletion docs/content/plugin-domains/policies.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
title: Policies & Access Control
order: 5
order: 6
group: plugin-domains
---

Expand Down
110 changes: 110 additions & 0 deletions docs/content/plugin-domains/profile-validation-limits.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
---
title: Profile Validation & Limits
order: 3
group: plugin-domains
---

# Profile Validation & Limits

UltimateAuth enforces framework-level safety limits on standard user profile fields and metadata.

These limits protect applications from excessively large profile values, unnecessary storage consumption, and resource exhaustion.

They apply consistently to the reference Users domain, regardless of whether InMemory or Entity Framework Core persistence is used.

## Profile Field Limits

The following maximum lengths are enforced:

| Field | Maximum characters |
|---|---:|
| FirstName | 128 |
| LastName | 128 |
| DisplayName | 256 |
| Gender | 128 |
| Bio | 4,096 |
| Language | 64 |
| TimeZone | 128 |
| Culture | 64 |

All fields are optional unless an application introduces additional requirements.

Limits are defined centrally in `UserProfileLimits` and cannot be increased through application configuration.

## Metadata Limits

User profiles support optional key-value metadata.

To prevent unbounded metadata storage, UltimateAuth enforces the following limits:

| Constraint | Maximum |
|---|---:|
| Metadata entries | 32 |
| Key length | 128 characters |
| Value length | 2,048 characters |
| Total metadata size | 8,192 UTF-8 bytes |

The total size includes metadata keys and values.

These limits apply to the complete metadata collection, not individually to each entry.

## Validation Enforcement

Profile safety limits are enforced during:

- User creation.
- Additional profile creation.
- Profile updates.
- Profile cloning.

Validation occurs before invalid data is persisted.

Framework-level safety validation is independent of `IUserProfileValidator`, ensuring that replacing the application validator cannot bypass these limits.

The same restrictions apply to both self-service and administrative operations.

## Custom Validation

Applications can implement `IUserProfileValidator` to introduce additional validation rules.

For example, an application may require a display name, restrict accepted languages, or enforce a shorter biography.

Custom validation can be stricter than the framework limits but cannot override the built-in safety boundaries.

The recommended approach is to use `IUserProfileValidator` for application-specific rules while relying on UltimateAuth for baseline safety validation.

## Validation Errors

When a profile exceeds the supported limits, UltimateAuth rejects the operation with a validation error.

Common error codes include:

| Error code | Meaning |
|---|---|
| `profile_field_too_long` | A standard profile field exceeds its maximum length. |
| `profile_metadata_entries_exceeded` | The metadata collection contains too many entries. |
| `profile_metadata_key_too_long` | A metadata key exceeds its maximum length. |
| `profile_metadata_value_too_long` | A metadata value exceeds its maximum length. |
| `profile_metadata_too_large` | The combined metadata size exceeds the allowed limit. |

Applications should use the error `Code` rather than relying on exception message text.

## Persistence Considerations

The reference Entity Framework Core implementation configures profile column lengths using the same framework limits.

Existing databases may require a migration when upgrading to a version that introduces these constraints.

Before applying restrictive database changes, review existing profile records for values exceeding the new limits.

The InMemory implementation uses the same application-level validation rules.

## Security Considerations

Profile limits are a defense-in-depth measure.

They do not replace HTTP request size limits, rate limiting, or application-specific validation.

Applications should configure these additional protections according to their deployment requirements.

For an overview of user identity and profile management, see [Users Domain](/docs/plugin-domains/users).
Loading
Loading