User Identity
Fabric authenticates an organization, not an end user — what matters to the network is that a transaction came from a node entitled to submit it. But in an application system we almost always need to know more: who exactly, under which organization, performed the operation. That is why hlf-core introduces its own user identity, which lives on top of the organizational one.
Identifiers are issued by UserUtil — in the format user_<time>_<hash>. The time here is zero-padded
for a reason: this way identifiers sort lexicographically by date, and enumerating users
simultaneously turns out to be an enumeration in chronological order. A separate method seed(...) builds
a deterministic system uid — it is needed to initialize a channel on behalf of the "system"
user, when real ones do not yet exist.
The user itself is described by the IUser<S, R> interface — with the fields uid, created, roles,
status and cryptoKey, parameterizable to one's own statuses and roles. And a crypto key is a pair of "value
and algorithm" (CryptoKey / ICryptoKey), and it is precisely this that links the user with the signature of their
commands. Your own domain model is obtained by inheriting IUser with concrete enums:
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;
}
The identifier itself is issued by UserUtil, and an important detail is that the time and hash arrive here not from
just anywhere, but from the transaction (stub.transaction). This is not a whim: chaincode executes in parallel on several
endorsing peers, and Date.now() or a random value would diverge between them, breaking consensus.
The only deterministic source of time and entropy is the transaction itself:
const user = UserUtil.create(User, holder.stub.transaction.date, holder.stub.transaction.hash);
// user.uid === "user_<transaction time>_<transaction hash>"
How this identity is used when verifying a command and protecting against replay is covered in the Security section, and how it is checked by a handler's guard — in Command Handlers.