Перейти к основному содержимому

Идентификация пользователя

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 обработчика — в «Обработчиках команд».