Skip to content
Closed
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
8 changes: 7 additions & 1 deletion Core/Resgrid.Config/OidcConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,20 @@ public static class OidcConfig

public static string Key = "";

public static string ConnectionString = "Server=rgdevserver;Database=ResgridOIDC;User Id=resgrid_odic;Password=resgrid123;MultipleActiveResultSets=True;TrustServerCertificate=True;";
public static string ConnectionString = "";

public static int AccessTokenExpiryMinutes = 1440;

public static int RefreshTokenExpiryDays = 365;

public static int NonMobileRefreshTokenExpiryDays = 2;

/// <summary>
/// Comma-separated, registered client IDs allowed to receive the longer mobile
/// refresh-token lifetime. Anonymous requests and caller-supplied scopes never qualify.
/// </summary>
public static string TrustedLongLivedClientIds = "";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Mutable static configuration in Core/Resgrid.Config/OidcConfig.cs and the related declarations at Web/Resgrid.Web/Models/AccountViewModels/ResetPasswordViewModel.cs:20-20, Core/Resgrid.Config/SessionSecurityConfig.cs:7-7, Core/Resgrid.Config/SessionSecurityConfig.cs:8-8, Core/Resgrid.Config/SessionSecurityConfig.cs:9-9, Core/Resgrid.Config/SessionSecurityConfig.cs:12-12, Core/Resgrid.Config/SessionSecurityConfig.cs:13-13, Core/Resgrid.Config/SessionSecurityConfig.cs:14-14, Core/Resgrid.Config/SessionSecurityConfig.cs:15-15, Core/Resgrid.Config/SessionSecurityConfig.cs:16-16, Core/Resgrid.Config/SessionSecurityConfig.cs:17-17, Core/Resgrid.Config/SessionSecurityConfig.cs:18-18, Core/Resgrid.Config/SessionSecurityConfig.cs:19-19, Core/Resgrid.Config/SessionSecurityConfig.cs:20-20, Core/Resgrid.Config/SessionSecurityConfig.cs:22-22, and Core/Resgrid.Services/DepartmentSettingsService.cs:27-27 obscures immutability and permits accidental reassignment. Mark TrustedLongLivedClientIds as readonly where the value is only supplied through configuration or bootstrap.

Kody rule violation: Use `readonly` or `const` for Immutable Data

public static readonly string TrustedLongLivedClientIds = string.Empty;
Prompt for LLM

File Core/Resgrid.Config/OidcConfig.cs:

Line 28:

Mutable static configuration in `Core/Resgrid.Config/OidcConfig.cs` and the related declarations at `Web/Resgrid.Web/Models/AccountViewModels/ResetPasswordViewModel.cs:20-20`, `Core/Resgrid.Config/SessionSecurityConfig.cs:7-7`, `Core/Resgrid.Config/SessionSecurityConfig.cs:8-8`, `Core/Resgrid.Config/SessionSecurityConfig.cs:9-9`, `Core/Resgrid.Config/SessionSecurityConfig.cs:12-12`, `Core/Resgrid.Config/SessionSecurityConfig.cs:13-13`, `Core/Resgrid.Config/SessionSecurityConfig.cs:14-14`, `Core/Resgrid.Config/SessionSecurityConfig.cs:15-15`, `Core/Resgrid.Config/SessionSecurityConfig.cs:16-16`, `Core/Resgrid.Config/SessionSecurityConfig.cs:17-17`, `Core/Resgrid.Config/SessionSecurityConfig.cs:18-18`, `Core/Resgrid.Config/SessionSecurityConfig.cs:19-19`, `Core/Resgrid.Config/SessionSecurityConfig.cs:20-20`, `Core/Resgrid.Config/SessionSecurityConfig.cs:22-22`, and `Core/Resgrid.Services/DepartmentSettingsService.cs:27-27` obscures immutability and permits accidental reassignment. Mark `TrustedLongLivedClientIds` as `readonly` where the value is only supplied through configuration or bootstrap.

Suggested Code:

		public static readonly string TrustedLongLivedClientIds = string.Empty;

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.


public static string EncryptionCert = "";

public static string SigningCert = "";
Expand Down
24 changes: 24 additions & 0 deletions Core/Resgrid.Config/SessionSecurityConfig.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
namespace Resgrid.Config
{
public static class SessionSecurityConfig
{
// Session tracking is required by the Web BFF and is safe for pre-feature
// credentials because they are adopted lazily by the validation middleware.
public static bool TrackingEnabled = true;
public static bool LegacyAdoptionEnabled = true;
public static string RequireSessionClaimForCredentialsIssuedAfterUtc = "";
// Blank is intentionally disabled at launch. Set to an ISO-8601 UTC timestamp
// only after previewing stored DepartmentSecurityPolicy session values.
public static string DepartmentSessionPolicyEnforcementAfterUtc = "";
public static int LastActivityWriteIntervalMinutes = 5;
public static int RevokedSessionRetentionDays = 90;
public static int PublicResetLinkLifetimeMinutes = 30;
public static int PublicResetAccountLimitPerHour = 3;
public static int PublicResetIpLimitPerHour = 10;
public static int WebBffAccessTokenLifetimeMinutes = 5;
public static int ClientMetadataMaximumLength = 256;
public static int UserAgentMaximumLength = 1024;
// Optional local JSON CIDR database. Leave blank to display location as unavailable.
public static string IpLocationDatabasePath = "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1080,4 +1080,10 @@
<data name="UnitStatusBaseType_8" xml:space="preserve">
<value>Returning</value>
</data>
</root>
<data name="RequirePasswordResetViaEmail" xml:space="preserve">
<value>Require password resets by email</value>
</data>
<data name="RequirePasswordResetViaEmailHelp" xml:space="preserve">
<value>Prevents department and group administrators from choosing or seeing a member's new password. Reset actions send the member a short-lived, single-use email link instead. Existing sessions are revoked after the member successfully changes the password.</value>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -675,4 +675,10 @@
<data name="UnitStatusBaseType_8" xml:space="preserve">
<value>Returning</value>
</data>
</root>
<data name="RequirePasswordResetViaEmail" xml:space="preserve">
<value>Require password resets by email</value>
</data>
<data name="RequirePasswordResetViaEmailHelp" xml:space="preserve">
<value>Prevents department and group administrators from choosing or seeing a member's new password. Reset actions send the member a short-lived, single-use email link instead. Existing sessions are revoked after the member successfully changes the password.</value>
</data>
</root>
3 changes: 3 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Profile/Profile.ar.resx
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>على الأقل {0} أحرف</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>رقم واحد على الأقل</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>حرف كبير وحرف صغير على الأقل</value></data>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>إرسال بريد إلكتروني لإعادة تعيين كلمة المرور</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>إرسال بريد إلكتروني لإعادة تعيين كلمة المرور</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>سيرسل Resgrid إلى هذا المستخدم رابطًا قصير الصلاحية يُستخدم مرة واحدة إلى عنوان بريده الإلكتروني المؤكد. لن يختار المسؤول كلمة المرور الجديدة ولن يراها.</value></data>
</root>
3 changes: 3 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Profile/Profile.de.resx
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>Mindestens {0} Zeichen lang</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>Mindestens eine Ziffer (Zahl)</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>Mindestens ein Groß- und ein Kleinbuchstabe</value></data>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>E-Mail zum Zurücksetzen des Passworts senden</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>E-Mail zum Zurücksetzen des Passworts senden</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Resgrid sendet diesem Benutzer einen kurzlebigen, einmal verwendbaren Link an seine bestätigte E-Mail-Adresse. Der Administrator wählt oder sieht das neue Passwort nicht.</value></data>
</root>
5 changes: 4 additions & 1 deletion Core/Resgrid.Localization/Areas/User/Profile/Profile.el.resx
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>Τουλάχιστον {0} χαρακτήρες</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>Τουλάχιστον ένα ψηφίο (αριθμός)</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>Τουλάχιστον ένα κεφαλαίο και ένα πεζό γράμμα</value></data>
</root>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>Αποστολή email επαναφοράς κωδικού πρόσβασης</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>Αποστολή email επαναφοράς κωδικού πρόσβασης</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Το Resgrid θα στείλει σε αυτόν τον χρήστη έναν σύνδεσμο σύντομης διάρκειας και μίας χρήσης στην επιβεβαιωμένη διεύθυνση email του. Ο διαχειριστής δεν θα επιλέξει ούτε θα δει τον νέο κωδικό πρόσβασης.</value></data>
</root>
5 changes: 4 additions & 1 deletion Core/Resgrid.Localization/Areas/User/Profile/Profile.en.resx
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>At least {0} characters long</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>At least one digit (number)</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>At least one uppercase and one lowercase letter</value></data>
</root>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>Send Password Reset Email</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>Send Password Reset Email</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Resgrid will send this user a short-lived, single-use link at their confirmed email address. The administrator will not choose or see the new password.</value></data>
</root>
5 changes: 4 additions & 1 deletion Core/Resgrid.Localization/Areas/User/Profile/Profile.es.resx
Original file line number Diff line number Diff line change
Expand Up @@ -300,4 +300,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>Al menos {0} caracteres</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>Al menos un dígito (número)</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>Al menos una letra mayúscula y una minúscula</value></data>
</root>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>Enviar correo de restablecimiento de contraseña</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>Enviar correo de restablecimiento de contraseña</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Resgrid enviará a este usuario un enlace de corta duración y de un solo uso a su dirección de correo confirmada. El administrador no elegirá ni verá la nueva contraseña.</value></data>
</root>
3 changes: 3 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Profile/Profile.fr.resx
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>Au moins {0} caractères</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>Au moins un chiffre</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>Au moins une lettre majuscule et une minuscule</value></data>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>Envoyer l’e-mail de réinitialisation du mot de passe</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>Envoyer l’e-mail de réinitialisation du mot de passe</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Resgrid enverra à cet utilisateur un lien à courte durée de vie et à usage unique à son adresse e-mail confirmée. L’administrateur ne choisira ni ne verra le nouveau mot de passe.</value></data>
</root>
3 changes: 3 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Profile/Profile.it.resx
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>Almeno {0} caratteri</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>Almeno una cifra (numero)</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>Almeno una lettera maiuscola e una minuscola</value></data>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>Invia email di reimpostazione password</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>Invia email di reimpostazione password</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Resgrid invierà a questo utente un link di breve durata e monouso al suo indirizzo email confermato. L’amministratore non sceglierà né vedrà la nuova password.</value></data>
</root>
3 changes: 3 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Profile/Profile.pl.resx
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>Co najmniej {0} znaków</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>Co najmniej jedna cyfra</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>Co najmniej jedna wielka i jedna mała litera</value></data>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>Wyślij wiadomość e-mail do resetowania hasła</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>Wyślij wiadomość e-mail do resetowania hasła</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Resgrid wyśle temu użytkownikowi krótkotrwały, jednorazowy link na potwierdzony adres e-mail. Administrator nie wybierze ani nie zobaczy nowego hasła.</value></data>
</root>
3 changes: 3 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Profile/Profile.sv.resx
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>Minst {0} tecken</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>Minst en siffra</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>Minst en stor och en liten bokstav</value></data>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>Skicka e-post för lösenordsåterställning</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>Skicka e-post för lösenordsåterställning</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Resgrid skickar en kortlivad engångslänk till användarens bekräftade e-postadress. Administratören väljer eller ser inte det nya lösenordet.</value></data>
</root>
3 changes: 3 additions & 0 deletions Core/Resgrid.Localization/Areas/User/Profile/Profile.uk.resx
Original file line number Diff line number Diff line change
Expand Up @@ -251,4 +251,7 @@
<data name="PasswordRequirementsLength" xml:space="preserve"><value>Щонайменше {0} символів</value></data>
<data name="PasswordRequirementsDigit" xml:space="preserve"><value>Щонайменше одна цифра</value></data>
<data name="PasswordRequirementsCase" xml:space="preserve"><value>Щонайменше одна велика та одна мала літера</value></data>
<data name="SendPasswordResetEmailHeader" xml:space="preserve"><value>Надіслати електронний лист для скидання пароля</value></data>
<data name="SendPasswordResetEmail" xml:space="preserve"><value>Надіслати електронний лист для скидання пароля</value></data>
<data name="SendPasswordResetEmailInfo" xml:space="preserve"><value>Resgrid надішле цьому користувачеві короткочасне одноразове посилання на підтверджену електронну адресу. Адміністратор не вибиратиме й не бачитиме новий пароль.</value></data>
</root>
4 changes: 3 additions & 1 deletion Core/Resgrid.Model/AuditLogTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ public enum AuditLogTypes
ModerationReportSubmitted,
ModerationRequestReopened,
ModerationRequestCompleted,
ModerationEvidenceDownloaded
ModerationEvidenceDownloaded,
PasswordResetByAdministrator,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Security audit coverage gap in Core/Resgrid.Model/AuditLogTypes.cs for PasswordResetByAdministrator and at Core/Resgrid.Model/AuditLogTypes.cs:196-196 can leave a privileged action without the required immutable and tamper-evident record structure. Ensure this audit action writes timestamp, actor.user_id, actor.role, action, resource.id, result, trace_id, ip, and user_agent, and store the records in append-only or WORM-backed logging forwarded to the SIEM.

Kody rule violation: Emit tamper-evident audit logs with required fields

Prompt for LLM

File Core/Resgrid.Model/AuditLogTypes.cs:

Line 195:

Security audit coverage gap in `Core/Resgrid.Model/AuditLogTypes.cs` for `PasswordResetByAdministrator` and at `Core/Resgrid.Model/AuditLogTypes.cs:196-196` can leave a privileged action without the required immutable and tamper-evident record structure. Ensure this audit action writes `timestamp`, `actor.user_id`, `actor.role`, `action`, `resource.id`, `result`, `trace_id`, `ip`, and `user_agent`, and store the records in append-only or WORM-backed logging forwarded to the SIEM.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Privileged password reset flow in Core/Resgrid.Model/AuditLogTypes.cs for PasswordResetByAdministrator lacks a step-up authentication requirement. Require MFA re-authentication within the last 5 minutes for this action and record mfa_verified_at in the audit log.

Kody rule violation: Require step-up MFA for privileged operations

Prompt for LLM

File Core/Resgrid.Model/AuditLogTypes.cs:

Line 195:

Privileged password reset flow in `Core/Resgrid.Model/AuditLogTypes.cs` for `PasswordResetByAdministrator` lacks a step-up authentication requirement. Require MFA re-authentication within the last 5 minutes for this action and record `mfa_verified_at` in the audit log.

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

UserAuthenticationSessionsRevoked
}
}
6 changes: 6 additions & 0 deletions Core/Resgrid.Model/DepartmentSettingTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,11 @@ public enum DepartmentSettingTypes
/// before the board highlights it.
/// </summary>
UnitStatusThresholds = 62,

/// <summary>
/// When enabled, department and group administrators cannot choose a member's new password.
/// Their reset action sends the member the hardened, single-use password recovery link instead.
/// </summary>
RequirePasswordResetViaEmail = 63,
}
}
11 changes: 11 additions & 0 deletions Core/Resgrid.Model/ExternalIdentityLinkMethod.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace Resgrid.Model
{
public enum ExternalIdentityLinkMethod
{
Subject = 0,
VerifiedEmail = 1,
TrustedSamlEmail = 2,
Scim = 3,
Administrator = 4
}
}
15 changes: 15 additions & 0 deletions Core/Resgrid.Model/Identity/IdentityUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,21 @@ public IdentityUser(string userName) : this()
//[ProtoMember(15)]
public override int AccessFailedCount { get; set; }

/// <summary>
/// Monotonically increases whenever all credentials for this account must be invalidated.
/// Existing accounts begin at zero so installing the session schema does not sign them out.
/// </summary>
public long AuthenticationGeneration { get; set; }

/// <summary>
/// Credentials issued at or before this UTC instant are invalid. This is also the
/// compatibility boundary for cookies and tokens issued before session tracking existed.
/// </summary>
public DateTime? CredentialsValidAfterUtc { get; set; }

/// <summary>UTC timestamp of the latest account-wide authentication state change.</summary>
public DateTime? AuthenticationStateChangedOn { get; set; }

[System.ComponentModel.DataAnnotations.Schema.NotMapped]
public string UserId
{
Expand Down
6 changes: 4 additions & 2 deletions Core/Resgrid.Model/Providers/IEmailProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ public interface IEmailProvider
{
void Configure(object sender, string fromAddress);

Task<bool> SendWelcomeMail(string name, string departmentName, string userName, string password, string email, int departmentId);
Task<bool> SendPasswordResetMail(string name, string password, string userName, string email, string departmentName);
Task<bool> SendWelcomeMail(string name, string departmentName, string userName, string email, int departmentId);
Task<bool> SendPasswordRecoveryMail(string name, string email, string departmentName,
string resetUrl, string ipAddress, string userAgent, string requestedOn, bool isSsoManaged);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

Sensitive metadata propagation in Core/Resgrid.Model/Providers/IEmailProvider.cs and the related usages at Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:26-26, Providers/Resgrid.Providers.Email/Template/PasswordChangedByAdministrator.html:25-25, Web/Resgrid.Web/Middleware/SessionValidationMiddleware.cs:101-101, Web/Resgrid.Web/Middleware/SessionValidationMiddleware.cs:131-131, Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:27-27, Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:38-38, Web/Resgrid.Web/Controllers/AccountController.cs:145-145, Core/Resgrid.Model/UserSession.cs:27-27, Web/Resgrid.Web.Services/Controllers/v4/ConnectController.cs:302-302, Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:40-40, Web/Resgrid.Web.Services/Controllers/v4/ConnectController.cs:309-309, Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:39-39, and Web/Resgrid.Web.Services/Controllers/v4/ConnectController.cs:327-327 increases the chance that ipAddress and userAgent are logged or persisted outside a dedicated audit path. Remove raw ipAddress and userAgent from the mail provider contract and pass only minimal non-identifying context such as a trace or request ID when needed.

Kody rule violation: Do not log PHI; mask and drop sensitive fields

string resetUrl, string requestedOn, bool isSsoManaged);
Prompt for LLM

File Core/Resgrid.Model/Providers/IEmailProvider.cs:

Line 12:

Sensitive metadata propagation in `Core/Resgrid.Model/Providers/IEmailProvider.cs` and the related usages at `Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:26-26`, `Providers/Resgrid.Providers.Email/Template/PasswordChangedByAdministrator.html:25-25`, `Web/Resgrid.Web/Middleware/SessionValidationMiddleware.cs:101-101`, `Web/Resgrid.Web/Middleware/SessionValidationMiddleware.cs:131-131`, `Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:27-27`, `Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:38-38`, `Web/Resgrid.Web/Controllers/AccountController.cs:145-145`, `Core/Resgrid.Model/UserSession.cs:27-27`, `Web/Resgrid.Web.Services/Controllers/v4/ConnectController.cs:302-302`, `Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:40-40`, `Web/Resgrid.Web.Services/Controllers/v4/ConnectController.cs:309-309`, `Providers/Resgrid.Providers.Email/Template/PasswordRecovery.html:39-39`, and `Web/Resgrid.Web.Services/Controllers/v4/ConnectController.cs:327-327` increases the chance that `ipAddress` and `userAgent` are logged or persisted outside a dedicated audit path. Remove raw `ipAddress` and `userAgent` from the mail provider contract and pass only minimal non-identifying context such as a trace or request ID when needed.

Suggested Code:

			string resetUrl, string requestedOn, bool isSsoManaged);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

kody code-review Kody Rules high

PII propagation risk in Core/Resgrid.Model/Providers/IEmailProvider.cs and the related usages at Web/Resgrid.Web/Controllers/AccountController.cs:145-145, Web/Resgrid.Web.Services/Middleware/SessionValidationHubFilter.cs:86-86, Web/Resgrid.Web.Services/Middleware/SessionValidationHubFilter.cs:94-94, Providers/Resgrid.Providers.Email/Template/PasswordChangedByAdministrator.html:26-26, Providers/Resgrid.Providers.Email/Template/PasswordChangedByAdministrator.html:25-25, Web/Resgrid.Web/Middleware/SessionValidationMiddleware.cs:101-101, Web/Resgrid.Web/Middleware/SessionValidationMiddleware.cs:131-131, Core/Resgrid.Model/UserSession.cs:39-39, Core/Resgrid.Model/UserSession.cs:40-40, and Web/Resgrid.Web.Services/Controllers/v4/SessionsController.cs:89-89 increases the likelihood that raw ipAddress and userAgent reach logs or telemetry without redaction. Keep those values out of the email provider interface, or replace them with redacted or hashed forms or a request or trace ID handled through structured logging.

Kody rule violation: Redact PII in logs and metrics by default

string resetUrl, string requestedOn, bool isSsoManaged);
Prompt for LLM

File Core/Resgrid.Model/Providers/IEmailProvider.cs:

Line 12:

PII propagation risk in `Core/Resgrid.Model/Providers/IEmailProvider.cs` and the related usages at `Web/Resgrid.Web/Controllers/AccountController.cs:145-145`, `Web/Resgrid.Web.Services/Middleware/SessionValidationHubFilter.cs:86-86`, `Web/Resgrid.Web.Services/Middleware/SessionValidationHubFilter.cs:94-94`, `Providers/Resgrid.Providers.Email/Template/PasswordChangedByAdministrator.html:26-26`, `Providers/Resgrid.Providers.Email/Template/PasswordChangedByAdministrator.html:25-25`, `Web/Resgrid.Web/Middleware/SessionValidationMiddleware.cs:101-101`, `Web/Resgrid.Web/Middleware/SessionValidationMiddleware.cs:131-131`, `Core/Resgrid.Model/UserSession.cs:39-39`, `Core/Resgrid.Model/UserSession.cs:40-40`, and `Web/Resgrid.Web.Services/Controllers/v4/SessionsController.cs:89-89` increases the likelihood that raw `ipAddress` and `userAgent` reach logs or telemetry without redaction. Keep those values out of the email provider interface, or replace them with redacted or hashed forms or a request or trace ID handled through structured logging.

Suggested Code:

			string resetUrl, string requestedOn, bool isSsoManaged);

Talk to Kody by mentioning @kody

Was this suggestion helpful? React with 👍 or 👎 to help Kody learn from this interaction.

Task<bool> SendPasswordChangedByAdministratorMail(string name, string userName, string email, string departmentName);
Task<bool> SendSignupMail(string name, string departmentName, string email);
Task<bool> SendMessageMail(string email, string subject, string messageSubject, string messageBody, string senderEmail, string senderName, string sentOn, int messageId);
Task<bool> SendCallMail(string email, string subject, string title, string priority, string natureOfCall, string mapPage,
Expand Down
14 changes: 0 additions & 14 deletions Core/Resgrid.Model/Repositories/IIdentityRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,6 @@ public interface IIdentityRepository
/// <returns>IdentityUser.</returns>
IdentityUser Update(IdentityUser user);

/// <summary>
/// Updates the username.
/// </summary>
/// <param name="oldUsername">The old username.</param>
/// <param name="newUsername">The new username.</param>
void UpdateUsername(string oldUsername, string newUsername);

/// <summary>
/// Adds the user to role.
/// </summary>
Expand Down Expand Up @@ -111,13 +104,6 @@ public interface IIdentityRepository
/// <returns>List&lt;UserGroupRole&gt;.</returns>
Task<List<UserGroupRole>> GetAllUsersGroupsAndRolesAsync(int departmentId, bool retrieveHidden, bool retrieveDisabled, bool retrieveDeleted);

/// <summary>
/// Updates the email.
/// </summary>
/// <param name="userId">The user identifier.</param>
/// <param name="newEmail">The new email.</param>
void UpdateEmail(string userId, string newEmail);

/// <summary>
/// Gets the user by user name asynchronous.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Resgrid.Model.Repositories
{
public interface IUserExternalIdentityLinksRepository : IRepository<UserExternalIdentityLink>
{
Task<UserExternalIdentityLink> GetActiveBySubjectAsync(string departmentSsoConfigId, string externalSubject);
Task<UserExternalIdentityLink> GetActiveByUserAndConfigAsync(string userId, string departmentSsoConfigId);
Task<IReadOnlyList<UserExternalIdentityLink>> GetActiveByUserAsync(string userId);
}
}
22 changes: 22 additions & 0 deletions Core/Resgrid.Model/Repositories/IUserSessionsRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Resgrid.Model.Repositories
{
public interface IUserSessionsRepository : IRepository<UserSession>
{
Task<IReadOnlyList<UserSession>> GetActiveByUserAsync(string userId, DateTime utcNow);
Task<UserSession> GetByAuthorizationIdAsync(string authorizationId);
Task<int> TouchAsync(string sessionId, DateTime occurredOn, DateTime writeBefore, string ipAddress,
string country, string region, string city, string userAgent, CancellationToken cancellationToken);
Task<int> UpdateDepartmentAsync(string targetUserId, string sessionId, int departmentId,
CancellationToken cancellationToken);
Task<int> RevokeAsync(string targetUserId, string sessionId, string actorUserId, int reason, DateTime revokedOn, CancellationToken cancellationToken);
Task<int> RevokeOthersAsync(string userId, string currentSessionId, int reason, DateTime revokedOn, CancellationToken cancellationToken);
Task<int> RevokeAllAsync(string targetUserId, string actorUserId, int reason, DateTime revokedOn, CancellationToken cancellationToken);
Task<int> RevokeDepartmentAsync(string targetUserId, int departmentId, int reason, DateTime revokedOn, CancellationToken cancellationToken);
Task<int> PurgeInactiveBeforeAsync(DateTime historyBeforeUtc, CancellationToken cancellationToken);
}
}
21 changes: 21 additions & 0 deletions Core/Resgrid.Model/Security/PasswordRecoveryContracts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System;

namespace Resgrid.Model.Security
{
public class PasswordRecoveryRequest
{
public string UserId { get; set; }
public string Email { get; set; }
public long AuthenticationGeneration { get; set; }
public string SecurityStampHash { get; set; }
public DateTime CreatedOn { get; set; }
public DateTime ExpiresOn { get; set; }
}

public class PasswordRecoveryIssueResult
{
public bool Issued { get; set; }
public bool RateLimited { get; set; }
public string Token { get; set; }
}
}
9 changes: 9 additions & 0 deletions Core/Resgrid.Model/Security/SessionClaimTypes.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace Resgrid.Model.Security
{
public static class SessionClaimTypes
{
public const string SessionId = "sid";
public const string AuthenticationGeneration = "auth_ver";
public const string WebEventingOnly = "web_eventing_only";
}
}
15 changes: 15 additions & 0 deletions Core/Resgrid.Model/Security/SessionCreationDeniedException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace Resgrid.Model.Security
{
public sealed class SessionCreationDeniedException : Exception
{
public SessionCreationDeniedException(string failureCode)
: base("The authentication session could not be created.")
{
FailureCode = failureCode;
}

public string FailureCode { get; }
}
}
Loading
Loading