Идентификация пользователя
Fabric аутентифицирует организацию, а не конечного пользователя — для сети важно, что транзакция пришла от узла, имеющего право её вносить. Но в прикладной системе нам почти всегда нужно знать большее: кто именно, за какой организацией, совершил операцию. Поэтому hlf-core вводит собственную идентичность пользователя, которая живёт поверх организационной.
Идентификаторы выдаёт UserUtil — в формате user_<время>_<hash>. Время здесь дополняется нулями
не случайно: так идентификаторы сортируются лексикографически по дате, и перечисление пользователей
заодно оказывается перечислением в хронологическом порядке. Отдельный метод seed(...) строит
детерминированный системный uid — он нужен, чтобы инициализировать канал от имени «системного»
пользователя, когда настоящих ещё нет.
Самого пользователя описывает интерфейс IUser<S, R> — с полями uid, created, roles,
status и cryptoKey, параметризуемый под свои статусы и роли. А криптоключ это пара «значение
и алгоритм» (CryptoKey / ICryptoKey), и именно он связывает пользователя с подписью его
команд. Своя доменная модель получается наследованием IUser с конкретными enum'ами:
import { IsEnum, IsOptional, Matches, IsDate } from 'class-validator';
import { Type } from 'class-transformer';
import { IUser, UserUtil, CryptoKey, ICryptoKey } from '@hlf-core/common';
export enum UserStatus {
ACTIVE = 'ACTIVE',
NON_ACTIVE = 'NON_ACTIVE'
}
export enum UserRole {
ADMIN = 'ADMIN',
USER_MANAGER = 'USER_MANAGER',
}
export class User implements IUser<UserStatus, UserRole> {
public static PREFIX = 'user';
@Matches(UserUtil.UID_REG_EXP)
public uid: string;
@Type(() => Date)
@IsDate()
public created: Date;
@IsEnum(UserStatus)
public status: UserStatus;
@IsOptional()
@IsEnum(UserRole, { each: true })
public roles?: Array<UserRole>;
@IsOptional()
@Type(() => CryptoKey)
public cryptoKey?: ICryptoKey;
}
Сам идентификатор выдаёт UserUtil, и важная деталь — время и хэш сюда приходят не откуда угодно,
а из транзакции (stub.transaction). Это не прихоть: chaincode исполняется параллельно на нескольких
пирах-эндорсерах, и Date.now() или случайное значение разошлись бы между ними, сломав консенсус.
Единственный детерминированный источник времени и энтропии — сама транзакция:
const user = UserUtil.create(User, holder.stub.transaction.date, holder.stub.transaction.hash);
// user.uid === "user_<время транзакции>_<hash транзакции>"
Как эта идентичность используется при проверке команды и защите от повторного воспроизведения — в разделе «Безопасность», а как её проверяет guard обработчика — в «Обработчиках команд».