mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2026-05-16 04:48:12 +02:00
(bugfix): redundant users.JwtTokensJson, fluent migrator improvements
This commit is contained in:
parent
86a31999bf
commit
c6d5b3fd1e
16
CHANGELOG.md
16
CHANGELOG.md
@ -4,6 +4,16 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [3.3.14] - 2026-04-26
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
|
||||||
|
- **Identity / PostgreSQL:** Removed redundant `users.JwtTokensJson` (historical JSON blob of sessions on the user row). **Server-side session allowlist semantics are unchanged:** issued sessions remain rows in `jwt_tokens` and are validated the same way as in Vault’s persisted `JwtToken` model—only the duplicate JSON encoding was dropped. New `users` inserts no longer hit `23502` on that column. FluentMigrator `DropUsersJwtTokensJson` (`20260426140000`) drops the column when present; the baseline no longer creates it; `JwtTokensTableMigrateFromJson` copies from JSON only if that column still exists (upgrades from older DBs).
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
|
||||||
|
- **FluentMigrator:** `RestoreUsersJwtTokensJsonIfDropped` (`20260426120000`) is now a no-op (revision kept for databases that already applied it). Session material is stored only in `jwt_tokens`, not duplicated as JSON on `users`.
|
||||||
|
|
||||||
## [3.3.13] - 2026-04-26
|
## [3.3.13] - 2026-04-26
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
@ -29,13 +39,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
- **Database:** FluentMigrator `RestoreUsersJwtTokensJsonIfDropped` (`20260426120000`) restores `users.JwtTokensJson` with `ADD COLUMN IF NOT EXISTS` when an older database had it removed by a prior `JwtTokensTableMigrateFromJson` revision.
|
- **Database:** FluentMigrator `RestoreUsersJwtTokensJsonIfDropped` (`20260426120000`) initially re-added `users.JwtTokensJson` with `ADD COLUMN IF NOT EXISTS` for databases that had dropped it under an older `JwtTokensTableMigrateFromJson` revision. **Superseded in 3.3.14:** that revision is a no-op and `DropUsersJwtTokensJson` drops the column; tokens stay in `jwt_tokens` only.
|
||||||
- **Helm / config:** `certsServerConfig.configuration.certsUIEngineConfiguration.autoSyncSchema` (default `true`) is rendered into server `appsettings.json` so add-only schema sync runs on every startup unless explicitly disabled.
|
- **Helm / config:** `certsServerConfig.configuration.certsUIEngineConfiguration.autoSyncSchema` (default `true`) is rendered into server `appsettings.json` so add-only schema sync runs on every startup unless explicitly disabled.
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- **Startup schema policy:** Documented expand-only expectations — FluentMigrator `Up()` should add tables/columns; avoid dropping renamed or legacy columns in `Up()`. `JwtTokensTableMigrateFromJson` no longer drops `JwtTokensJson` (tokens remain in `jwt_tokens`; legacy JSON column may remain for audit).
|
- **Startup schema policy:** Documented expand-only expectations — FluentMigrator `Up()` should add tables/columns; avoid dropping renamed columns in routine `Up()` without an explicit follow-up plan. `JwtTokensTableMigrateFromJson` no longer drops `JwtTokensJson` in that revision’s `Up()` (tokens are normalized into `jwt_tokens`). **3.3.14** removes `JwtTokensJson` from the live schema via `DropUsersJwtTokensJson`.
|
||||||
- **Schema sync:** `AutoSyncSchema` defaults to **true** in repo `appsettings.json`; `SchemaSyncService` desired map includes `users.IsActive`, `TwoFactorSharedKey`, and optional `JwtTokensJson` for additive repair. Still **ADD COLUMN IF NOT EXISTS** only (no DROP).
|
- **Schema sync:** `AutoSyncSchema` defaults to **true** in repo `appsettings.json`; `SchemaSyncService` desired map includes `users.IsActive` and `TwoFactorSharedKey`. **3.3.14** stops treating `JwtTokensJson` as a desired column. Still **ADD COLUMN IF NOT EXISTS** only (no DROP in sync).
|
||||||
- **ICertsEngineConfiguration / ISchemaSyncService:** Clarified that add-only sync is recommended and describes the no-DROP guarantee.
|
- **ICertsEngineConfiguration / ISchemaSyncService:** Clarified that add-only sync is recommended and describes the no-DROP guarantee.
|
||||||
|
|
||||||
## [3.3.10] - 2026-04-26
|
## [3.3.10] - 2026-04-26
|
||||||
|
|||||||
@ -3,13 +3,13 @@ using MaksIT.Core.Abstractions.Dto;
|
|||||||
namespace MaksIT.CertsUI.Engine.Dto.Identity;
|
namespace MaksIT.CertsUI.Engine.Dto.Identity;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// PostgreSQL <c>users</c> row (Linq2DB). JWT sessions live in <c>jwt_tokens</c>; <see cref="JwtTokens"/> is not a mapped column.
|
/// PostgreSQL <c>users</c> row (Linq2DB). JWT sessions are rows in <c>jwt_tokens</c>; <see cref="JwtTokens"/> is loaded separately, not a column on <c>users</c>.
|
||||||
/// A legacy <c>JwtTokensJson</c> column may still exist on the table for expand-only migrations; it is not mapped here.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class UserDto : DtoDocumentBase<Guid> {
|
public class UserDto : DtoDocumentBase<Guid> {
|
||||||
public required string Name { get; set; }
|
public required string Name { get; set; }
|
||||||
public required string Salt { get; set; }
|
public required string Salt { get; set; }
|
||||||
public required string Hash { get; set; }
|
public required string Hash { get; set; }
|
||||||
|
|
||||||
public DateTime LastLoginUtc { get; set; }
|
public DateTime LastLoginUtc { get; set; }
|
||||||
public bool IsActive { get; set; } = true;
|
public bool IsActive { get; set; } = true;
|
||||||
public string? TwoFactorSharedKey { get; set; }
|
public string? TwoFactorSharedKey { get; set; }
|
||||||
|
|||||||
@ -25,7 +25,6 @@ public class BaselineCertsSchema : Migration {
|
|||||||
.WithColumn("Name").AsCustom("text").NotNullable()
|
.WithColumn("Name").AsCustom("text").NotNullable()
|
||||||
.WithColumn("Salt").AsCustom("text").NotNullable()
|
.WithColumn("Salt").AsCustom("text").NotNullable()
|
||||||
.WithColumn("Hash").AsCustom("text").NotNullable()
|
.WithColumn("Hash").AsCustom("text").NotNullable()
|
||||||
.WithColumn("JwtTokensJson").AsCustom("text").NotNullable()
|
|
||||||
.WithColumn("LastLoginUtc").AsDateTimeOffset().NotNullable();
|
.WithColumn("LastLoginUtc").AsDateTimeOffset().NotNullable();
|
||||||
|
|
||||||
Create.Index("IX_users_Name").OnTable("users").OnColumn("Name").Unique();
|
Create.Index("IX_users_Name").OnTable("users").OnColumn("Name").Unique();
|
||||||
|
|||||||
@ -4,8 +4,8 @@ using FluentMigrator;
|
|||||||
namespace MaksIT.CertsUI.Engine.FluentMigrations;
|
namespace MaksIT.CertsUI.Engine.FluentMigrations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Normalizes JWT refresh/access tokens into <c>jwt_tokens</c> (one row per token). Legacy <c>users.JwtTokensJson</c> is retained
|
/// Normalizes JWT refresh/access tokens into <c>jwt_tokens</c> (one row per token).
|
||||||
/// (expand-only policy); the app reads <c>jwt_tokens</c> only.
|
/// If <c>users.JwtTokensJson</c> exists (older databases), rows are copied from that JSON; otherwise this step only creates <c>jwt_tokens</c>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Migration(20260418100000)]
|
[Migration(20260418100000)]
|
||||||
public class JwtTokensTableMigrateFromJson : Migration {
|
public class JwtTokensTableMigrateFromJson : Migration {
|
||||||
@ -29,24 +29,26 @@ public class JwtTokensTableMigrateFromJson : Migration {
|
|||||||
Create.Index("IX_jwt_tokens_Token").OnTable("jwt_tokens").OnColumn("Token");
|
Create.Index("IX_jwt_tokens_Token").OnTable("jwt_tokens").OnColumn("Token");
|
||||||
Create.Index("IX_jwt_tokens_RefreshToken").OnTable("jwt_tokens").OnColumn("RefreshToken");
|
Create.Index("IX_jwt_tokens_RefreshToken").OnTable("jwt_tokens").OnColumn("RefreshToken");
|
||||||
|
|
||||||
Execute.Sql("""
|
if (Schema.Table("users").Column("JwtTokensJson").Exists()) {
|
||||||
INSERT INTO "jwt_tokens" ("Id", "UserId", "Token", "RefreshToken", "IssuedAt", "ExpiresAt", "RefreshTokenExpiresAt", "IsRevoked")
|
Execute.Sql("""
|
||||||
SELECT
|
INSERT INTO "jwt_tokens" ("Id", "UserId", "Token", "RefreshToken", "IssuedAt", "ExpiresAt", "RefreshTokenExpiresAt", "IsRevoked")
|
||||||
(elem->>'Id')::uuid,
|
SELECT
|
||||||
u."Id",
|
(elem->>'Id')::uuid,
|
||||||
COALESCE(elem->>'Token', ''),
|
u."Id",
|
||||||
COALESCE(elem->>'RefreshToken', ''),
|
COALESCE(elem->>'Token', ''),
|
||||||
(elem->>'IssuedAt')::timestamptz,
|
COALESCE(elem->>'RefreshToken', ''),
|
||||||
(elem->>'ExpiresAt')::timestamptz,
|
(elem->>'IssuedAt')::timestamptz,
|
||||||
(elem->>'RefreshTokenExpiresAt')::timestamptz,
|
(elem->>'ExpiresAt')::timestamptz,
|
||||||
COALESCE((elem->>'IsRevoked')::boolean, false)
|
(elem->>'RefreshTokenExpiresAt')::timestamptz,
|
||||||
FROM "users" u
|
COALESCE((elem->>'IsRevoked')::boolean, false)
|
||||||
CROSS JOIN LATERAL json_array_elements(
|
FROM "users" u
|
||||||
CASE WHEN u."JwtTokensJson" IS NULL OR btrim(u."JwtTokensJson"::text) = '' THEN '[]'::json
|
CROSS JOIN LATERAL json_array_elements(
|
||||||
ELSE u."JwtTokensJson"::json END
|
CASE WHEN u."JwtTokensJson" IS NULL OR btrim(u."JwtTokensJson"::text) = '' THEN '[]'::json
|
||||||
) AS elem
|
ELSE u."JwtTokensJson"::json END
|
||||||
WHERE (elem->>'Id') IS NOT NULL
|
) AS elem
|
||||||
""");
|
WHERE (elem->>'Id') IS NOT NULL
|
||||||
|
""");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Down() =>
|
public override void Down() =>
|
||||||
|
|||||||
@ -3,15 +3,13 @@ using FluentMigrator;
|
|||||||
namespace MaksIT.CertsUI.Engine.FluentMigrations;
|
namespace MaksIT.CertsUI.Engine.FluentMigrations;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Databases that already applied <see cref="JwtTokensTableMigrateFromJson"/> when it still dropped <c>JwtTokensJson</c>
|
/// Previously re-added <c>users.JwtTokensJson</c> when a prior migration had dropped that JSON column.
|
||||||
/// get the column back (empty default). Expand-only: we never remove renamed/legacy columns in <c>Up()</c>.
|
/// <see cref="DropUsersJwtTokensJson"/> removes the column; persisted sessions use <c>jwt_tokens</c> only (same role as Vault’s <c>JwtToken</c> rows).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Migration(20260426120000)]
|
[Migration(20260426120000)]
|
||||||
public class RestoreUsersJwtTokensJsonIfDropped : Migration {
|
public class RestoreUsersJwtTokensJsonIfDropped : Migration {
|
||||||
public override void Up() {
|
public override void Up() {
|
||||||
Execute.Sql("""
|
// No-op: revision kept so databases that already applied the old DDL remain valid; see DropUsersJwtTokensJson.
|
||||||
ALTER TABLE "users" ADD COLUMN IF NOT EXISTS "JwtTokensJson" text NOT NULL DEFAULT '';
|
|
||||||
""");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Down() =>
|
public override void Down() =>
|
||||||
|
|||||||
@ -0,0 +1,17 @@
|
|||||||
|
using FluentMigrator;
|
||||||
|
|
||||||
|
namespace MaksIT.CertsUI.Engine.FluentMigrations;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Drops <c>users.JwtTokensJson</c> when present (old JSON copy of sessions). Sessions remain in <c>jwt_tokens</c> for server-side validation / allowlist behavior.
|
||||||
|
/// </summary>
|
||||||
|
[Migration(20260426140000)]
|
||||||
|
public class DropUsersJwtTokensJson : Migration {
|
||||||
|
public override void Up() {
|
||||||
|
if (Schema.Table("users").Column("JwtTokensJson").Exists())
|
||||||
|
Delete.Column("JwtTokensJson").FromTable("users");
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Down() =>
|
||||||
|
throw new NotSupportedException("Down is not supported; restore from backup if required.");
|
||||||
|
}
|
||||||
@ -83,7 +83,6 @@ public class SchemaSyncService(ICertsEngineConfiguration config, ILogger<SchemaS
|
|||||||
("Name", "text"),
|
("Name", "text"),
|
||||||
("Salt", "text"),
|
("Salt", "text"),
|
||||||
("Hash", "text"),
|
("Hash", "text"),
|
||||||
("JwtTokensJson", "text"),
|
|
||||||
("LastLoginUtc", "timestamp with time zone"),
|
("LastLoginUtc", "timestamp with time zone"),
|
||||||
("IsActive", "boolean"),
|
("IsActive", "boolean"),
|
||||||
("TwoFactorSharedKey", "text"),
|
("TwoFactorSharedKey", "text"),
|
||||||
@ -168,7 +167,6 @@ public class SchemaSyncService(ICertsEngineConfiguration config, ILogger<SchemaS
|
|||||||
|
|
||||||
if (table.Equals("users", StringComparison.OrdinalIgnoreCase)) {
|
if (table.Equals("users", StringComparison.OrdinalIgnoreCase)) {
|
||||||
if (column.Equals("TwoFactorSharedKey", StringComparison.OrdinalIgnoreCase)) return false;
|
if (column.Equals("TwoFactorSharedKey", StringComparison.OrdinalIgnoreCase)) return false;
|
||||||
if (column.Equals("JwtTokensJson", StringComparison.OrdinalIgnoreCase)) return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<Version>3.3.13</Version>
|
<Version>3.3.14</Version>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user