⟨/⟩ 50_Domain/Access/Tokens.cs

82 строк · в начало

using System;
using Ban.Sdaid.Notation.Documents;

namespace Ban.Sdaid.Icd.Domain.Access
{
    /// <summary>Активный токен — учёт выписанного при входе JWT по его уникальному jti.</summary>
    [DomainEntity]
    public class UserActiveToken
    {
        /// <summary>Идентификатор записи.</summary>
        public Guid Id { get; init; }

        /// <summary>Уникальный идентификатор токена (claim jti) — ключ сессии и ключ кэша контекста.</summary>
        public string Jti { get; init; }

        /// <summary>Момент выписки.</summary>
        public DateTime IssuedAt { get; init; }

        /// <summary>Момент истечения.</summary>
        public DateTime ExpiresAt { get; init; }
    }

    /// <summary>Отозванный токен — учёт вышедших/аннулированных сессий по jti (чёрный список).</summary>
    [DomainEntity]
    public class UserInvalidatedToken
    {
        /// <summary>Идентификатор записи.</summary>
        public Guid Id { get; init; }

        /// <summary>jti отозванного токена.</summary>
        public string Jti { get; init; }

        /// <summary>Момент отзыва.</summary>
        public DateTime InvalidatedAt { get; init; }

        /// <summary>Причина отзыва (выход, административный отзыв).</summary>
        public string Reason { get; init; }
    }

    /// <summary>Описание доменных сущностей <see cref="UserActiveToken"/> и <see cref="UserInvalidatedToken"/>.</summary>
    public class UserTokenSpec : IDomainEntityDocument
    {
        public Type Entity => typeof(UserActiveToken);

        public string Name => "Токены сессии";

        public string Description =>
            @"Учёт выписанных и отозванных токенов по jti. Даёт настоящий выход: токен можно аннулировать до истечения, а его контекст — вытеснить из кэша.";

        public string Version => "0.1";
        public string Status => "draft";
        public string[] Comments => new string[0];

        public static string S1_Purpose = """
            ## Назначение

            JWT сам по себе действителен до истечения — отозвать его нельзя. Чтобы выход был настоящим,
            Сервис ведёт две парные записи по `jti`:

            - **активный токен** — появляется при входе; по нему кэшируется контекст пользователя;
            - **отозванный токен** — появляется при выходе/аннулировании; запрос с таким `jti`
              отклоняется (401), даже если срок ещё не вышел.

            `jti` — общий ключ: он и в токене, и в записи, и в ключе кэша контекста.
            """;

        public static string S2_Invariants = """
            ## Инварианты

            - `jti` уникален среди активных токенов.
            - Выход переносит запись из активных в отозванные и вытесняет контекст из кэша.
            - Истёкшие активные записи подчищаются (не копятся бесконечно).
            """;

        public static string S99_Related = $"""
            ## Связанные артефакты

            - {nameof(Ban.Sdaid.Icd.Arch.Tech.CSharp.ADR_SRV_006_AuthImplementation)} — выпуск, кэш контекста, чёрный список.
            """;
    }
}