- Хранилище транзакций — состояние транзакций входа/подтверждения (Transaction Engine).
- База OpenIddict — клиенты и токены OIDC-сервера.
Хранилище транзакций
Настраивается вConfigureTransactionEngine. Выберите один стор.
Program.cs
Сам метод
UseEfCoreStore приезжает сателлитным пакетом — ядро не тащит EF Core транзитивно.
Провайдер EF Core (UseNpgsql, UseSqlServer) и его NuGet-пакет принадлежат хост-приложению —
ядро провайдер-агностично. Сборка миграций, названная в MigrationsAssembly(...), приезжает
ещё одним пакетом — без него схема не создастся:MigrationsAssembly(...):
Журнал аудита ведёт историю миграций в собственной таблице, чтобы делить БД с хранилищем транзакций:
рядом с
MigrationsAssembly(...) в UseEfCoreSink передайте
MigrationsHistoryTable(AuditMigrationDefaults.MigrationsHistoryTable).
Параметры Redis (RedisTransactionStoreOptions):
Configuration— строка подключения (например,localhost:6379).KeyPrefix— префикс ключей транзакций (по умолчаниюveriqa:tx:).
Канальные хранилища: несколько реплик
Канальные адаптеры держат пять собственных хранилищ. По умолчанию они in-process — этого достаточно для одной реплики, но не для двух:
Разворачиваете больше одной реплики — подключите Redis-реализации всех пяти хранилищ одним
вызовом на билдере канальных адаптеров:
Program.cs
UseRedisChannelStores всё остаётся
in-process — реализации по умолчанию не подменяются.
Параметры (RedisChannelStoreOptions):
Configuration— строка подключения. Оставьте пустой, чтобы намеренно переиспользоватьIConnectionMultiplexer, зарегистрированный в контейнере кем-то ещё. Задать её при таком мультиплексоре нельзя: хост падает на старте ошибкой конфигурации, называющей обе стороны их адресами, номером логической БД, настройками TLS и именем мастера sentinel (учётные данные не печатаются), — иначе заданная строка молча игнорировалась бы, а канальные хранилища уехали бы в чужой Redis. Проверка выполняется на старте хоста, поэтому порядок двух регистраций значения не имеет.PromptKeyPrefix— префикс координат подсказки (по умолчаниюveriqa:ch:prompt:).EmailActionTokenKeyPrefix— префикс action-токенов (по умолчаниюveriqa:ch:email:token:).EmailPushCorrelationKeyPrefix— префикс корреляций Push (по умолчаниюveriqa:ch:email:corr:).ProcessedMessageKeyPrefix— префикс отметок обработанных писем (по умолчаниюveriqa:ch:email:msg:).EmailClaimCompletionKeyPrefix— префикс хранилища добора email (по умолчаниюveriqa:ch:email:claim:). Адреса и учётки канала в его ключах — отпечатки SHA-256, коды и токены ссылок хранятся только отпечатками.PhoneRequestCorrelationKeyPrefix— префикс запросов номера телефона (по умолчаниюveriqa:ch:phone:). Остаток ключа — хеш SHA-256 чата, так что идентификатор пользователя канала открытым текстом не хранится.ProcessedMessageRetention— срок хранения отметки «письмо обработано» (по умолчанию 24 часа).
Срок жизни записей задаёт сама сущность: токен и корреляция живут ровно до своего
ExpiresAt, координаты подсказки — до истечения самой долгой возможной транзакции плюс запас на
публикацию события истечения (координаты потребляет это событие, а публикует его фоновая уборка
уже после того, как транзакция истекла). Единственное исключение — ProcessedMessageRetention:
у отметки о письме собственной сущности нет.Канальные идентичности: в поставке не хранятся
ПортIChannelIdentityRepository (движок транзакций) сохраняет идентичность канала —
channel_user_id, телефон, email, отображаемое имя — upsert’ом по тройке (TenantId,
ChannelType, ChannelUserId). Контракт не предусматривает ни удаления, ни TTL: запись живёт
бессрочно.
Реализации этого порта в поставке нет, и Veriqa её не регистрирует. Без вашей регистрации не
сохраняется ничего, и вход при этом работает: с транзакцией едет снапшот резолюции идентичности.
Хранить эти данные (PII, бессрочно) — ваше решение; зарегистрируйте свою реализацию, и каждый вход
становится upsert’ом:
Program.cs
Scoped-реализация над DbContext безопасна наравне с Singleton. Референсная
in-memory реализация лежит в сэмпле samples/dotnet/inproc/login.
Полная картина «что где лежит и сколько живёт», включая этот порт, — в
Безопасности и обработке данных.
Хранилище собранных claims
Когда входу не хватает claim, который приложение требует, Veriqa добирает его сама — телефон запросом в канале, email кодом или ссылкой, поле своей формой (см. Обогащение claim’ов). Без хранилища всё это живёт столько же, сколько транзакция, и следующий вход спрашивает снова. Хранилище собранных claims держит это по тенанту иsub: что собрала Veriqa, от каких Optional claims пользователь отказался, время
последнего входа, прочитавшего записи, и sub, который получило приложение, если его claims mapper
этот sub поменял. Значения, которые даёт сам канал, не сохраняются.
Пока вы его не включили, не хранится ничего. На отдельном сервере это один ключ,
Veriqa:CollectedClaims:Store:Enabled:
appsettings.json
Enabled: true без
Veriqa:TransactionEngine:Store:ConnectionString останавливает старт с сообщением, называющим оба
ключа.
Во встроенном режиме хранилище — отдельная регистрация, и базу для него выбираете вы:
Program.cs
CollectedClaimsDbContext,
и применяются на старте так же, как миграции транзакций. Не убирайте
MigrationsHistoryTable(CollectedClaimsMigrationDefaults.MigrationsHistoryTable): хранилище может
делить базу с транзакциями, и общая таблица истории смешала бы два набора миграций. Свою реализацию
ICollectedClaimsStore подключает te.UseCollectedClaimsStore<TStore>() — и она получает всё
описанное ниже: эндпоинт, вызов хоста и автоудаление — ровно как реализация на EF Core.
Удаление по sub
Когда пользователь удалил аккаунт или потребовал стереть данные, вы как оператор персональных
данных удаляете всё, что хранилище о нём держит, — все его записи и выданные приложениям sub. Операция одна, входов два; оба идемпотентны: sub, по которому ничего нет, — успех.
Серверный эндпоинт — для приложения, которое не встраивает Veriqa как .NET-библиотеку:
AllowClientCredentials и AllowCollectedClaimsDeletion (см. Клиенты;
флаг без AllowClientCredentials останавливает старт), токен — по grant_type=client_credentials,
как у API подтверждений. sub передаётся в той
форме, в какой его получило приложение: названное в client_id, а без параметра — вызывающий
клиент. sub, выданные этому приложению, ищутся в тенанте вызывающего; не найденный среди них
sub считается ядровым. Своего ограничения частоты у эндпоинта нет — выдавайте флаг только одному
своему серверному клиенту.
Вызов хоста — для приложения, в которое Veriqa встроена:
ICollectedClaimsDeletion регистрируется вместе с хранилищем; без хранилища его в контейнере нет.
Каждое удаление пишет запись CollectedClaimsDeleted в журнал аудита,
когда журнал включён: актор — вызывающий клиент для эндпоинта и system для вызова хоста и
автоудаления; цель — маскированный ядровый sub; в метаданных — тенант, клиент и основание:
endpoint, host_call или retention. Значения claims в запись не попадают. С выключенным журналом
удаление работает и ничего не пишет. После удаления следующий вход этого пользователя идёт как
первый: Veriqa снова спрашивает недостающее, включая Optional claim, от которого он раньше
отказался.
Удаление неиспользуемых записей
Записиsub, по которому никто не входил заданное число дней, можно удалять автоматически. Срок
отсчитывается от последнего входа, прочитавшего записи, а не от времени сбора значения. По
умолчанию выключено:
appsettings.json
0 выключает; отрицательное значение останавливает старт. На уровне тенанта член
ClaimCompletionCollectedClaimsRetentionDays замещает глобальное значение, так что очистку можно
включить для одного тенанта (ClaimCompletion.CollectedClaimsRetentionDays в
каталоге объявлений). Очистка идёт на
старте и дальше раз в час, пачками по 100; вход, прочитавший записи между выборкой и удалением,
их сохраняет. Каждый удалённый sub получает запись аудита с основанием retention.
База OpenIddict
Клиенты и токены OIDC-сервера хранятся отдельно и настраиваются секциейVeriqa:OpenIddict:Database:
appsettings.json
Коннектор на одном инстансе без базы данных
Когда Veriqa подключена к другому провайдеру идентичности — Keycloak или вашему собственному, — она участвует во входе и ни в чём после него: запрос авторизации, подтверждение в канале, обмен кода и сразу за ним вызов userinfo. Сессия и её refresh-токены дальше принадлежат тому провайдеру, а аккаунтов пользователей Veriqa не хранит и так (см. Канальные идентичности). Такое развёртывание может работать одним инстансом со всеми хранилищами в памяти. В self-hosted режиме:appsettings.json
Veriqa:TransactionEngine:Store:ConnectionStringне задавайте — транзакции остаются в памяти;Veriqa:Logging:Modeоставьте по умолчанию или задайте любым, кромеAudit;- клиента объявите без
AllowRefreshTokens(по умолчанию выключен) и безoffline_access.
authServer.UseInMemoryOpenIddictStore() и
te.UseInMemoryStore(). Хост стартует с Warning о волатильном хранилище OpenIddict — для этого
варианта это ожидаемо.
Чего стоит рестарт:
Деплой, обновление образа и перезапуск контейнера — всё это рестарты: каждый стоит первой строки таблицы.
Другая СУБД и правка модели
Standalone-образ несёт PostgreSQL и SQL Server. Всё остальное — выбор вашего хоста во встроенном режиме, правки Veriqa для этого не нужны.Своя сборка миграций
Четыре контекста EF Core публичны:TransactionDbContext
(Veriqa.Core.TransactionEngine.Store.EfCore), CollectedClaimsDbContext
(Veriqa.Core.TransactionEngine.CollectedClaims.EfCore), AuditDbContext
(Veriqa.Core.AuditTrail.Store.EfCore) и OpenIddictDbContext (Veriqa.Core.AuthServer.Data). Это якоря для миграций, а не API доступа к
данным — таблицы остаются внутренними. Для СУБД, под которую Veriqa миграций не поставляет:
- Создайте библиотеку классов со ссылками на ваш провайдер EF Core,
Microsoft.EntityFrameworkCore.Designи пакет, в котором лежит контекст. - Добавьте
IDesignTimeDbContextFactory<T>на каждый контекст — через неёdotnet efстроит контекст. - Выполните
dotnet ef migrations add Initial --project <ваша библиотека> --context <контекст>и назовите библиотеку вMigrationsAssembly(...)в рантайме.
TransactionDbContextDesignTimeFactory.cs
AuditDbContext рядом с MigrationsAssembly(...) добавьте
MigrationsHistoryTable(AuditMigrationDefaults.MigrationsHistoryTable) — и в фабрике, и в рантайме;
для CollectedClaimsDbContext так же —
MigrationsHistoryTable(CollectedClaimsMigrationDefaults.MigrationsHistoryTable).
Правка модели
Схема, collation, тип колонки — модель меняется штатнымIModelCustomizer EF Core, а не правкой
контекстов Veriqa. Унаследуйтесь от RelationalModelCustomizer:
VeriqaModelCustomizer.cs
ReplaceService<IModelCustomizer, …>() в том же делегате, что выбирает
провайдер:
Program.cs
Публикация событий транзакций
In-process доставка вITransactionEventHandler (см.
Настройку) — часть движка, отдельного
вызова не требует: журнал аудита, push статуса на страницу входа и очистка промптов получают
события в любой конфигурации. Публикация событий наружу — отдельный и дополняющий выбор
транспорта, тоже в ConfigureTransactionEngine:
Program.cs
RabbitMqEventPublisherOptions: ConnectionString (обязателен, amqp://…), ExchangeName
(по умолчанию veriqa.transactions), плюс параметры очереди и переподключения (QueueCapacity,
MaxReconnectAttempts, ReconnectBaseDelaySeconds).
Надёжность того, что уходит из процесса, равна надёжности выбранной шины — ни больше ни меньше.
Своей гарантии доставки поверх транспорта Veriqa не строит: на in-process канале событие может
потеряться при аварии, а с очередью сообщений доставка ровно такая, какую даёт эта очередь.
Дальше
Hardening к продакшну
Миграции, секреты, ротация ключей.
Настройка и кастомизация
Каналы, дизайн, локализация, точки расширения.