File

src/issuer/status-list/status-list.service.ts

Index

Methods

Constructor

constructor(configService: ConfigService, cryptoService: CryptoService, statusMappingRepository: Repository<StatusMapping>, statusListRepository: Repository<StatusListEntity>)
Parameters :
Name Type Optional
configService ConfigService No
cryptoService CryptoService No
statusMappingRepository Repository<StatusMapping> No
statusListRepository Repository<StatusListEntity> No

Methods

Async createEntry
createEntry(session: Session, credentialConfigurationId: string)

Get the next free entry in the status list.

Parameters :
Name Type Optional
session Session No
credentialConfigurationId string No
Returns : Promise<JWTwithStatusListPayload>
Async createList
createList(entry: StatusListEntity)

Create a new status list and stored it in the file

Parameters :
Name Type Optional
entry StatusListEntity No
Returns : any
getList
getList(tenantId: string)

Get the JWT for the status list of a tenant.

Parameters :
Name Type Optional Description
tenantId string No

The ID of the tenant.

Returns : any

The JWT for the status list.

onTenantDelete
onTenantDelete(tenantId: string)

Delete the status list for a specific tenant.

Parameters :
Name Type Optional Description
tenantId string No

The ID of the tenant.

Returns : void
Async onTenantInit
onTenantInit(tenantId: string)

Initialize the status list service by checking if the status list file exists. If it does not exist, create a new status list with 10,000 entries and a stack of 10,000 indexes. The stack is shuffled to ensure randomness in the order of entries. The status list is stored in the file system as a JSON file.

Parameters :
Name Type Optional
tenantId string No
Returns : any
Private Async setEntry
setEntry(id: number, value: number, tenantId: string)

Update the value of an entry in the status list

Parameters :
Name Type Optional
id number No
value number No
tenantId string No
Returns : unknown
Async updateStatus
updateStatus(value: StatusUpdateDto, tenantId: string)

Update the status of a session and its credential configuration

Parameters :
Name Type Optional
value StatusUpdateDto No
tenantId string No
Returns : any
import { ConflictException, Injectable } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { InjectRepository } from "@nestjs/typeorm";
import {
    createHeaderAndPayload,
    JWTwithStatusListPayload,
    StatusList,
    StatusListJWTHeaderParameters,
} from "@sd-jwt/jwt-status-list";
import { JwtPayload } from "@sd-jwt/types";
import { join } from "path";
import { Repository } from "typeorm";
import { CryptoService } from "../../crypto/crypto.service";
import { Session } from "../../session/entities/session.entity";
import { StatusUpdateDto } from "./dto/status-update.dto";
import { StatusListEntity } from "./entities/status-list.entity";
import { StatusMapping } from "./entities/status-mapping.entity";

@Injectable()
export class StatusListService {
    constructor(
        private configService: ConfigService,
        private cryptoService: CryptoService,
        @InjectRepository(StatusMapping)
        private statusMappingRepository: Repository<StatusMapping>,
        @InjectRepository(StatusListEntity)
        private statusListRepository: Repository<StatusListEntity>,
    ) {}

    /**
     * Initialize the status list service by checking if the status list file exists.
     * If it does not exist, create a new status list with 10,000 entries and a stack
     * of 10,000 indexes. The stack is shuffled to ensure randomness in the order of
     * entries. The status list is stored in the file system as a JSON file.
     */
    async onTenantInit(tenantId: string) {
        const size = 10000;
        // create an empty array with the size of 1000
        const elements = new Array(size).fill(0).map(() => 0);
        // create a list of 1000 indexes and shuffel them
        const stack = new Array(size)
            .fill(0)
            .map((_, i) => i)
            .sort(() => 0.5 - Math.random());

        const entry = await this.statusListRepository.save({
            tenantId,
            elements,
            stack,
            bits: 1,
        });

        await this.createList(entry);
    }

    /**
     * Delete the status list for a specific tenant.
     * @param tenantId The ID of the tenant.
     */
    onTenantDelete(tenantId: string) {
        this.statusListRepository.delete({ tenantId });
        this.statusMappingRepository.delete({ tenantId });
    }

    /**
     * Create a new status list and stored it in the file
     */
    async createList(entry: StatusListEntity) {
        const list = new StatusList(entry.elements, entry.bits);
        const iss = `${this.configService.getOrThrow<string>("PUBLIC_URL")}`;

        const sub = join(
            this.configService.getOrThrow<string>("PUBLIC_URL"),
            entry.tenantId,
            "status-management",
            "status-list",
        );

        const prePayload: JwtPayload = {
            iss,
            sub,
            iat: Math.floor(Date.now() / 1000),
        };
        const preHeader: StatusListJWTHeaderParameters = {
            alg: "ES256",
            typ: "statuslist+jwt",
            x5c: await this.cryptoService.getCertChain(
                "signing",
                entry.tenantId,
            ),
        };
        const { header, payload } = createHeaderAndPayload(
            list,
            prePayload,
            preHeader,
        );

        const jwt = await this.cryptoService.signJwt(
            header,
            payload,
            entry.tenantId,
        );
        await this.statusListRepository.update(
            { tenantId: entry.tenantId },
            { jwt },
        );
    }

    /**
     * Get the JWT for the status list of a tenant.
     * @param tenantId The ID of the tenant.
     * @returns The JWT for the status list.
     */
    getList(tenantId: string) {
        return this.statusListRepository
            .findOneByOrFail({ tenantId })
            .then((file) => file.jwt);
    }

    /**
     * Get the next free entry in the status list.
     * @returns
     */
    async createEntry(
        session: Session,
        credentialConfigurationId: string,
    ): Promise<JWTwithStatusListPayload> {
        const file = await this.statusListRepository
            .findOneByOrFail({
                tenantId: session.tenantId,
            })
            //if none if found, create one
            .then(() =>
                this.onTenantInit(session.tenantId).then(() =>
                    this.statusListRepository.findOneByOrFail({
                        tenantId: session.tenantId,
                    }),
                ),
            );
        // get the last element from the stack
        const idx = file.stack.pop();
        //TODO: what to do if the stack is empty
        if (idx === undefined) {
            throw new Error("Stack for status list is empty!!!");
        }
        const sub = join(
            this.configService.getOrThrow<string>("PUBLIC_URL"),
            session.tenantId,
            "status-management",
            "status-list",
        );
        // store the index in the status mapping
        await this.statusMappingRepository.save({
            tenantId: session.tenantId,
            sessionId: session.id,
            index: idx,
            list: sub,
            credentialConfigurationId,
        });

        return {
            status: {
                status_list: {
                    idx: idx,
                    uri: sub,
                },
            },
        };
    }

    /**
     * Update the value of an entry in the status list
     * @param id
     * @param value
     */
    private async setEntry(id: number, value: number, tenantId: string) {
        const entry = await this.statusListRepository.findOneByOrFail({
            tenantId,
        });
        entry.elements[id] = value;
        await this.statusListRepository.update(
            { tenantId },
            { elements: entry.elements },
        );
        return this.createList(entry);
    }

    /**
     * Update the status of a session and its credential configuration
     * @param value
     */
    async updateStatus(value: StatusUpdateDto, tenantId: string) {
        const entries = await this.statusMappingRepository.findBy({
            sessionId: value.sessionId,
            credentialConfigurationId: value.credentialConfigurationId,
        });
        if (entries.length === 0) {
            throw new ConflictException(
                `No status mapping found for session ${value.sessionId} and credential configuration ${value.credentialConfigurationId}`,
            );
        }
        for (const entry of entries) {
            await this.setEntry(entry.index, value.status, tenantId);
        }
    }
}

results matching ""

    No results matching ""