Skip to content

Conversation

@ucswift
Copy link
Member

@ucswift ucswift commented Sep 10, 2025

Summary by CodeRabbit

  • New Features

    • Stronger password rules (min 8 chars, uppercase, lowercase, digit) on registration and user password change; tightened login field validation.
    • Added Countly analytics support and enriched user details; events now forwarded to Aptabase and Countly when enabled.
  • Bug Fixes

    • Improved device registration validation to prevent missing device IDs.
  • Chores

    • Added Dropzone assets and Countly SDK; updated internal CSS/JS bundles.
  • Tests

    • Added examples demonstrating password complexity validation.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Sep 10, 2025

Walkthrough

Adds Countly telemetry configuration and web SDK initialization; extends analytics JS to forward events to Countly and Aptabase. Introduces password complexity validation utilities and attribute, applies to registration and user models. Tightens login/username length rules. Adjusts PushService registration guard to require DeviceId. Adds Dropzone and Countly assets via libman and bundles. Adds test examples.

Changes

Cohort / File(s) Summary
Telemetry configuration
Core/Resgrid.Config/TelemetryConfig.cs
Adds public static CountlyUrl and CountlyWebKey fields; no changes to key resolution methods.
Password complexity framework
Core/Resgrid.Framework/PasswordComplexityAttribute.cs, Core/Resgrid.Framework/StringHelpers.cs
Adds PasswordComplexityAttribute and supporting StringHelpers APIs (VerifyPasswordComplexity, IsValidPassword) plus PasswordComplexityResult record.
Web model validation updates
Web/Resgrid.Web/Areas/User/Models/AddPersonModel.cs, Web/Resgrid.Web/Models/AccountViewModels/RegisterViewModel.cs, Web/Resgrid.Web/Models/AccountViewModels/LoginViewModel.cs
Applies stricter length rules and new PasswordComplexity attribute (min length 8; require upper/lower/digit) to NewPassword/Password; adds username length constraint.
Analytics integration (web)
Web/Resgrid.Web/Areas/User/Views/Shared/_UserLayout.cshtml, Web/Resgrid.Web/wwwroot/js/app/common/analytics/resgrid.common.analytics.js
Conditionally loads and initializes Countly SDK in layout; analytics module now registers user/session with Countly and forwards track events to Aptabase and Countly when available.
Push registration guard
Core/Resgrid.Services/PushService.cs
Adds DeviceId non-empty check to RegisterUnit; minor style change for IsNullOrWhiteSpace usage.
Assets and package management
Web/Resgrid.Web/libman.json, Web/Resgrid.Web/Startup.cs
Adds Countly SDK and Dropzone via libman; includes Dropzone in internal CSS/JS bundles; formatting-only adjustments for other bundles.
Tests/examples
Tests/Resgrid.Tests/Framework/PasswordComplexityExamples.cs
Adds example/test helper demonstrating password complexity verification and a convenience validator.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant Browser as Browser (View)
  participant MVC as MVC Model Binder
  participant Attr as PasswordComplexityAttribute
  participant Helpers as StringHelpers

  User->>Browser: Submit form with NewPassword/Password
  Browser->>MVC: POST model data
  MVC->>Attr: Validate(value, context)
  Attr->>Helpers: VerifyPasswordComplexity(password, rules)
  Helpers-->>Attr: PasswordComplexityResult(IsValid, Errors)
  alt valid
    Attr-->>MVC: ValidationResult.Success
    MVC-->>Browser: Proceed (next action)
  else invalid
    Attr-->>MVC: ValidationResult(errors)
    MVC-->>Browser: Return model errors
  end
Loading
sequenceDiagram
  autonumber
  participant Page as _UserLayout.cshtml
  participant CSDK as Countly SDK
  participant App as resgrid.common.analytics
  Note over Page: If CountlyUrl & CountlyWebKey set
  Page->>CSDK: Load countly.min.js and init({app_key,url})
  App->>CSDK: set_id(userId), user_details
  App->>CSDK: track_sessions(), track_pageview()
  App->>PostHog: capture(event, props)
  App->>Aptabase: trackEvent(event, props)
  App->>CSDK: add_event({key:event, segmentation:props})
Loading
sequenceDiagram
  autonumber
  actor Client
  participant Service as PushService
  Client->>Service: RegisterUnit(pushUri)
  alt Missing DeviceId or UnitId or PushLocation
    Service-->>Client: return false
  else Valid input
    Service->>Service: Continue registration flow
    Service-->>Client: return result
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Pre-merge checks (1 passed, 2 warnings)

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The title references both Countly integration and password validation fixes, making it unfocused and overly broad, and uses a non-descriptive abbreviation (“PW”) along with the ticket code, which detracts from clarity. Please revise the title to concisely summarize the primary change (for example “Integrate Countly telemetry into analytics”) or split the PR into separate requests for distinct features.
Docstring Coverage ⚠️ Warning Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

Poem

A rabbit taps keys with delighted might,
New guards for passwords, strong and tight.
Countly hops in, events take flight,
Dropzone packs, all assets right.
Push IDs checked by moonlit light—
Ship it now, and sleep tonight. 🐇✨

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch develop

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@request-info
Copy link

request-info bot commented Sep 10, 2025

Thanks for opening this, but we'd appreciate a little more information. Could you update it with more details?

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (15)
Web/Resgrid.Web/libman.json (1)

289-291: Library additions LGTM; consider scoping files for Dropzone

  • Countly entry is correctly pinned and file-scoped.
  • Dropzone pulls the entire package; to reduce payload, consider adding a files array (e.g., dist/dropzone.min.js, dist/dropzone.css).
 {
-  "library": "@deltablot/[email protected]",
-  "destination": "wwwroot/lib/deltablot/dropzone/"
+  "library": "@deltablot/[email protected]",
+  "destination": "wwwroot/lib/deltablot/dropzone/",
+  "files": [
+    "dist/dropzone.min.js",
+    "dist/dropzone.css"
+  ]
 }

Also applies to: 293-299

Core/Resgrid.Services/PushService.cs (2)

38-39: Guarding on DeviceId is good; add trace for early returns

Returning false without logging makes prod issues hard to diagnose. Add a concise log for null/empty DeviceId/PushLocation.

- if (pushUri == null || string.IsNullOrWhiteSpace(pushUri.DeviceId) || string.IsNullOrWhiteSpace(pushUri.PushLocation))
-  return false;
+ if (pushUri == null || string.IsNullOrWhiteSpace(pushUri.DeviceId) || string.IsNullOrWhiteSpace(pushUri.PushLocation))
+ {
+   Framework.Logging.LogWarning("PushService.Register invalid args: " +
+     $"nullUri={pushUri == null}, deviceIdEmpty={string.IsNullOrWhiteSpace(pushUri?.DeviceId)}, pushLocationEmpty={string.IsNullOrWhiteSpace(pushUri?.PushLocation)}");
+   return false;
+ }

64-65: Unit registration: same ask—log why the guard fails

Mirror logging here to aid ops visibility.

- if (pushUri == null || !pushUri.UnitId.HasValue || string.IsNullOrWhiteSpace(pushUri.DeviceId) || string.IsNullOrWhiteSpace(pushUri.PushLocation))
-  return false;
+ if (pushUri == null || !pushUri.UnitId.HasValue || string.IsNullOrWhiteSpace(pushUri.DeviceId) || string.IsNullOrWhiteSpace(pushUri.PushLocation))
+ {
+   Framework.Logging.LogWarning("PushService.RegisterUnit invalid args: " +
+     $"nullUri={pushUri == null}, unitIdHasValue={pushUri?.UnitId.HasValue}, deviceIdEmpty={string.IsNullOrWhiteSpace(pushUri?.DeviceId)}, pushLocationEmpty={string.IsNullOrWhiteSpace(pushUri?.PushLocation)}");
+   return false;
+ }
Core/Resgrid.Config/TelemetryConfig.cs (1)

19-21: Countly keys added; consider first-class exporter support

If you later want to toggle providers uniformly, extend TelemetryExporters with Countly and wire GetAnalyticsKey or a GetEnabledExporters helper. Not blocking.

 public enum TelemetryExporters
 {
   None = 0,
   PostHog = 1,
-  Aptabase = 2
+  Aptabase = 2,
+  Countly = 3
 }
Web/Resgrid.Web/Areas/User/Views/Shared/_UserLayout.cshtml (1)

62-73: Initialize Countly safely and avoid render-blocking

  • Add defer to the script tag to avoid blocking head parsing.
  • Ensure Countly.q exists before analytics code uses it.
-        <script type='text/javascript' src="~/lib/countly-sdk-web/lib/countly.min.js"></script>
+        <script defer src="~/lib/countly-sdk-web/lib/countly.min.js"></script>
         <script type='text/javascript'>
             if (Countly && window["Countly"]) {
+                Countly.q = Countly.q || [];
                 Countly.init({
                   app_key: "@Resgrid.Config.TelemetryConfig.CountlyWebKey",
                   url: "@Resgrid.Config.TelemetryConfig.CountlyUrl"
                 });
             }
         </script>
Web/Resgrid.Web/wwwroot/js/app/common/analytics/resgrid.common.analytics.js (3)

16-28: Countly: prefer direct API when available; keep q-push as fallback

Use direct methods when the SDK is loaded, fall back to q.push if not. Also explicitly set count: 1 in events.

-                    if (typeof Countly !== "undefined") {
-                        Countly.q.push(['set_id', userId]);
-                        Countly.q.push(['user_details', {
-                            "name": name,
-                            "email": email,
-                            "organization": departmentName,
-                            "custom": {
-                                "createdOn": new Date(createdOn * 1000),
-                                "departmentId": departmentId
-                            }]);
-                        Countly.q.push(['track_sessions']);
-                        Countly.q.push(['track_pageview']);
-                    }
+                    if (typeof Countly !== "undefined") {
+                        var C = Countly;
+                        if (C.user_details) {
+                            if (C.set_id) C.set_id(userId);
+                            C.user_details({
+                                name: name,
+                                email: email,
+                                organization: departmentName,
+                                custom: {
+                                    createdOn: new Date(createdOn * 1000),
+                                    departmentId: departmentId
+                                }
+                            });
+                            if (C.track_sessions) C.track_sessions();
+                            if (C.track_pageview) C.track_pageview();
+                        } else if (C.q && C.q.push) {
+                            C.q.push(['set_id', userId]);
+                            C.q.push(['user_details', {
+                                name: name,
+                                email: email,
+                                organization: departmentName,
+                                custom: {
+                                    createdOn: new Date(createdOn * 1000),
+                                    departmentId: departmentId
+                                }
+                            }]);
+                            C.q.push(['track_sessions']);
+                            C.q.push(['track_pageview']);
+                        }
+                    }

33-36: Minor: terminate statement

Add a semicolon to keep linters quiet.

-                        posthog.capture(event)
+                        posthog.capture(event);

38-46: Forwarding to Countly: add explicit count and direct-call fallback

Be explicit with count: 1 and prefer add_event directly when available.

-                    if (typeof Countly !== 'undefined') {
-                        Countly.q.push(['add_event', {
-                            "key": event
-                        }]);
-                    }
+                    if (typeof Countly !== 'undefined') {
+                        var C = Countly;
+                        if (C.add_event) {
+                            C.add_event({ key: event, count: 1 });
+                        } else if (C.q && C.q.push) {
+                            C.q.push(['add_event', { key: event, count: 1 }]);
+                        }
+                    }
Core/Resgrid.Framework/StringHelpers.cs (1)

257-329: Password complexity helper looks solid; consider localization

Error strings are hard-coded; consider pulling from Resgrid.Localization to localize validation messages and centralize wording.

Tests/Resgrid.Tests/Framework/PasswordComplexityExamples.cs (2)

20-28: Fix incorrect “valid” example against default rules (min length = 8).

“Abc123” is 6 chars, so it fails the default 8-char rule. Update the sample to avoid misleading output/comments.

-				"Abc123", // Valid according to default requirements
+				"Abcd1234", // Valid according to default requirements

45-55: Consider converting console examples into executable unit tests.

Turn these into xUnit/NUnit/MSTest assertions so regressions are caught automatically and CI-visible.

I can draft a minimal test class mirroring these cases if you want.

Web/Resgrid.Web/Models/AccountViewModels/RegisterViewModel.cs (1)

40-46: Clarify length validation message to cover both min and max.

Current StringLength message mentions only the minimum; it will also surface on >100 chars. Make the message accurate.

-		[StringLength(100, ErrorMessage = "The password must be at least 8 characters long", MinimumLength = 8)]
+		[StringLength(100, MinimumLength = 8, ErrorMessage = "The password must be between 8 and 100 characters long.")]
Web/Resgrid.Web/Areas/User/Models/AddPersonModel.cs (1)

46-52: Align length validation message with both bounds.

Same issue as RegisterViewModel: message says “at least 8,” but will trigger for >100 too.

-		[StringLength(100, ErrorMessage = "The password must be at least 8 characters long", MinimumLength = 8)]
+		[StringLength(100, MinimumLength = 8, ErrorMessage = "The password must be between 8 and 100 characters long.")]
Core/Resgrid.Framework/PasswordComplexityAttribute.cs (2)

24-40: Remove duplicate override; keep a single source of truth.

Overriding both IsValid methods duplicates logic and risks divergence. Rely on the ValidationResult override only.

-		public override bool IsValid(object value)
-		{
-			if (value is not string password)
-			{
-				return false;
-			}
-
-			var result = StringHelpers.VerifyPasswordComplexity(
-				password, 
-				MinLength, 
-				RequireUppercase, 
-				RequireLowercase, 
-				RequireDigit, 
-				RequireSpecialChar);
-
-			return result.IsValid;
-		}
+		// Remove boolean override to avoid logic drift; ValidationResult override below is authoritative.

42-64: Avoid duplicate “required” errors by skipping validation on null/empty.

When combined with [Required], the current logic adds an extra “empty password” message from VerifyPasswordComplexity. Early-out keeps messages clean.

-		protected override ValidationResult IsValid(object value, ValidationContext validationContext)
+		protected override ValidationResult IsValid(object value, ValidationContext validationContext)
 		{
-			if (value is not string password)
-			{
-				return new ValidationResult("Invalid password format");
-			}
+			// Let [Required] handle null/empty scenarios to avoid duplicate messages.
+			if (value is null) return ValidationResult.Success;
+			if (value is not string password) return ValidationResult.Success;
+			if (string.IsNullOrWhiteSpace(password)) return ValidationResult.Success;
 
 			var result = StringHelpers.VerifyPasswordComplexity(
 				password, 
 				MinLength, 
 				RequireUppercase, 
 				RequireLowercase, 
 				RequireDigit, 
 				RequireSpecialChar);
 
 			if (result.IsValid)
 			{
 				return ValidationResult.Success;
 			}
 
 			var errorMessage = string.Join(", ", result.Errors);
 			return new ValidationResult(errorMessage, new[] { validationContext.MemberName });
 		}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8b3f079 and 2b24b51.

📒 Files selected for processing (12)
  • Core/Resgrid.Config/TelemetryConfig.cs (1 hunks)
  • Core/Resgrid.Framework/PasswordComplexityAttribute.cs (1 hunks)
  • Core/Resgrid.Framework/StringHelpers.cs (2 hunks)
  • Core/Resgrid.Services/PushService.cs (2 hunks)
  • Tests/Resgrid.Tests/Framework/PasswordComplexityExamples.cs (1 hunks)
  • Web/Resgrid.Web/Areas/User/Models/AddPersonModel.cs (2 hunks)
  • Web/Resgrid.Web/Areas/User/Views/Shared/_UserLayout.cshtml (1 hunks)
  • Web/Resgrid.Web/Models/AccountViewModels/LoginViewModel.cs (1 hunks)
  • Web/Resgrid.Web/Models/AccountViewModels/RegisterViewModel.cs (2 hunks)
  • Web/Resgrid.Web/Startup.cs (1 hunks)
  • Web/Resgrid.Web/libman.json (1 hunks)
  • Web/Resgrid.Web/wwwroot/js/app/common/analytics/resgrid.common.analytics.js (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.cs

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.cs: Prefer functional patterns and immutable data where appropriate in C#
Use modern C# features appropriately
Use meaningful, descriptive names for types, methods, and parameters; avoid unclear abbreviations
Separate state from behavior (e.g., use records for state and place operations in separate static classes)
Prefer pure methods over methods with side effects
Use extension methods appropriately for domain-specific operations
Design for testability; avoid hidden dependencies inside methods and prefer explicit, pure functions
Minimize constructor injection; keep the number of injected dependencies small
Prefer composition with interfaces to extend behavior

Files:

  • Core/Resgrid.Config/TelemetryConfig.cs
  • Web/Resgrid.Web/Models/AccountViewModels/RegisterViewModel.cs
  • Core/Resgrid.Framework/StringHelpers.cs
  • Tests/Resgrid.Tests/Framework/PasswordComplexityExamples.cs
  • Core/Resgrid.Services/PushService.cs
  • Web/Resgrid.Web/Startup.cs
  • Core/Resgrid.Framework/PasswordComplexityAttribute.cs
  • Web/Resgrid.Web/Models/AccountViewModels/LoginViewModel.cs
  • Web/Resgrid.Web/Areas/User/Models/AddPersonModel.cs
🧬 Code graph analysis (3)
Core/Resgrid.Framework/StringHelpers.cs (1)
Core/Resgrid.Framework/PasswordComplexityAttribute.cs (1)
  • IsValid (24-40)
Tests/Resgrid.Tests/Framework/PasswordComplexityExamples.cs (2)
Core/Resgrid.Framework/StringHelpers.cs (1)
  • StringHelpers (22-331)
Core/Resgrid.Framework/PasswordComplexityAttribute.cs (1)
  • IsValid (24-40)
Core/Resgrid.Framework/PasswordComplexityAttribute.cs (1)
Core/Resgrid.Framework/StringHelpers.cs (1)
  • StringHelpers (22-331)
🪛 Biome (2.1.2)
Web/Resgrid.Web/wwwroot/js/app/common/analytics/resgrid.common.analytics.js

[error] 25-25: expected , but instead found ]

Remove ]

(parse)


[error] 25-25: expected , but instead found ;

Remove ;

(parse)


[error] 26-26: expected , but instead found (

Remove (

(parse)


[error] 26-26: expected , but instead found ;

Remove ;

(parse)


[error] 27-27: expected , but instead found (

Remove (

(parse)


[error] 27-27: expected , but instead found ;

Remove ;

(parse)


[error] 38-38: expected , but instead found if

Remove if

(parse)


[error] 38-38: Expected a parameter but instead found 'typeof'.

Expected a parameter here.

(parse)


[error] 38-38: expected , but instead found Aptabase

Remove Aptabase

(parse)


[error] 38-38: expected , but instead found !==

Remove !==

(parse)


[error] 42-42: expected , but instead found if

Remove if

(parse)


[error] 42-42: Expected a parameter but instead found 'typeof'.

Expected a parameter here.

(parse)


[error] 42-42: expected , but instead found Countly

Remove Countly

(parse)


[error] 42-42: expected , but instead found !==

Remove !==

(parse)


[error] 38-40: This method is later overwritten by an object member with the same name.

Overwritten with this method.

If an object property with the same name is defined multiple times (except when combining a getter with a setter), only the last definition makes it into the object and previous definitions are ignored.
Unsafe fix: Remove this method.

(lint/suspicious/noDuplicateObjectKeys)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: build
  • GitHub Check: Analyze (csharp)
  • GitHub Check: Analyze (javascript-typescript)
🔇 Additional comments (4)
Web/Resgrid.Web/Startup.cs (4)

353-355: Formatting-only changes in public bundles look good

No functional changes; safe to merge.


357-359: Formatting-only changes in Angular bundle look good

No functional changes; safe to merge.


361-370: Add Dropzone CSS is correct; order is appropriate

The Dropzone CSS path matches libman destination and is placed before site style.css, allowing site overrides. LGTM.


372-382: Add Dropzone JS is correct; bundle order is fine

The JS path matches libman destination. Placing Dropzone before site.min.js is sensible.

Comment on lines +8 to +9
[StringLength(250, ErrorMessage = "The username must be at least 2 characters long and contain only alphanumeric characters.", MinimumLength = 2)]
public string Username { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Username error message contradicts actual allowed characters

Identity allows spaces and -._@ (see Startup configuration). Either adjust the message or add a regex to enforce the identity-allowed set. Suggested: simplify the message.

-        [StringLength(250, ErrorMessage = "The username must be at least 2 characters long and contain only alphanumeric characters.", MinimumLength = 2)]
+        [StringLength(250, ErrorMessage = "The username must be at least 2 characters long.", MinimumLength = 2)]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[StringLength(250, ErrorMessage = "The username must be at least 2 characters long and contain only alphanumeric characters.", MinimumLength = 2)]
public string Username { get; set; }
[StringLength(250, ErrorMessage = "The username must be at least 2 characters long.", MinimumLength = 2)]
public string Username { get; set; }
🤖 Prompt for AI Agents
In Web/Resgrid.Web/Models/AccountViewModels/LoginViewModel.cs around lines 8-9,
the StringLength error message incorrectly states allowed characters
(alphanumeric only) while Identity permits spaces and -._@; either simplify the
message to only describe length (e.g., "The username must be between 2 and 250
characters.") or add a RegularExpression attribute that matches the exact
allowed character set used in Startup (include spaces and - . _ @ plus
alphanumerics) so the validation message and enforcement align.

Comment on lines +12 to 14
[StringLength(100, ErrorMessage = "The password must be at least 8 characters long, include a number (digit) and an uppercase letter", MinimumLength = 4)]
[DataType(DataType.Password)]
public string Password { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Login password length constraint may block valid users

Enforcing min length here can prevent sign-in for legacy accounts whose passwords predate current policy. Recommend removing StringLength (validation occurs on change/reset), or align MinimumLength with current policy (8) if you’re certain no existing accounts fall below.

-        [StringLength(100, ErrorMessage = "The password must be at least 8 characters long, include a number (digit) and an uppercase letter", MinimumLength = 4)]
-        [DataType(DataType.Password)]
+        [DataType(DataType.Password)]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
[StringLength(100, ErrorMessage = "The password must be at least 8 characters long, include a number (digit) and an uppercase letter", MinimumLength = 4)]
[DataType(DataType.Password)]
public string Password { get; set; }
[DataType(DataType.Password)]
public string Password { get; set; }
🤖 Prompt for AI Agents
In Web/Resgrid.Web/Models/AccountViewModels/LoginViewModel.cs around lines
12-14, the StringLength attribute on Password enforces a minimum length (4) at
sign-in which can block legacy accounts; remove the StringLength attribute
entirely so login does not validate password length (perform length validation
only on password change/reset), or if you are sure no existing accounts use
shorter passwords, update MinimumLength to 8 to match current policy.

@ucswift
Copy link
Member Author

ucswift commented Sep 10, 2025

Approve

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is approved.

@ucswift ucswift merged commit d0d01c0 into master Sep 10, 2025
16 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants