-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: FTL-16681 add commands to manage credential issuance configurat…
…ion (#400)
- Loading branch information
Showing
11 changed files
with
621 additions
and
1 deletion.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
import { readFile } from 'fs/promises' | ||
import { | ||
IssuanceConfigDto, | ||
CreateIssuanceConfigInput, | ||
CredentialSupportedObject, | ||
} from '@affinidi-tdk/credential-issuance-client' | ||
import { WalletDto, CreateWalletInput } from '@affinidi-tdk/wallets-client' | ||
import { input, select } from '@inquirer/prompts' | ||
import { ux, Flags } from '@oclif/core' | ||
import { CLIError } from '@oclif/core/errors' | ||
import z from 'zod' | ||
import { BaseCommand } from '../../common/base-command.js' | ||
import { DidMethods } from '../../common/constants.js' | ||
import { promptRequiredParameters } from '../../common/prompts.js' | ||
import { INPUT_LIMIT, validateInputLength } from '../../common/validators.js' | ||
import { issuanceService } from '../../services/affinidi/cis/service.js' | ||
import { cweService } from '../../services/affinidi/cwe/service.js' | ||
|
||
export class CreateIssuanceConfig extends BaseCommand<typeof CreateIssuanceConfig> { | ||
static summary = 'Creates credential issuance configuration in your active project' | ||
static examples = [ | ||
'<%= config.bin %> <%= command.id %> -n <value> -w <value> -f credentialSchemas.json', | ||
'<%= config.bin %> <%= command.id %> --name <value> --wallet-id <value> --description <value> --credential-offer-duration <value> --file credentialSchemas.json', | ||
] | ||
static flags = { | ||
name: Flags.string({ | ||
char: 'n', | ||
summary: 'Name of the credential issuance configuration', | ||
}), | ||
description: Flags.string({ | ||
char: 'd', | ||
summary: 'Description of the credential issuance configuration', | ||
}), | ||
'wallet-id': Flags.string({ | ||
char: 'w', | ||
summary: 'ID of the wallet', | ||
}), | ||
'credential-offer-duration': Flags.integer({ | ||
summary: 'Credential offer duration in seconds', | ||
}), | ||
file: Flags.string({ | ||
char: 'f', | ||
summary: | ||
'Location of a json file containing the list of allowed schemas for creating a credential offer. One or more schemas can be added to the issuance. The credential type ID must be unique', | ||
}), | ||
} | ||
|
||
public async run(): Promise<IssuanceConfigDto> { | ||
const { flags } = await this.parse(CreateIssuanceConfig) | ||
|
||
const promptFlags = await promptRequiredParameters(['wallet-id', 'name', 'file'], flags) | ||
|
||
const flagsSchema = z.object({ | ||
'wallet-id': z.string().max(INPUT_LIMIT), | ||
name: z.string().min(3).max(INPUT_LIMIT), | ||
description: z.string().max(INPUT_LIMIT).optional(), | ||
'credential-offer-duration': z.number().optional(), | ||
file: z.string(), | ||
}) | ||
const validatedFlags = flagsSchema.parse(promptFlags) | ||
|
||
ux.action.start('Fetching wallets') | ||
const { wallets } = await cweService.listWallets() | ||
ux.action.stop('Fetched successfully!') | ||
|
||
let issuerWalletId = validatedFlags['wallet-id'] | ||
|
||
const walletIds = wallets?.map((wallet: WalletDto) => wallet.id) || [] | ||
const wrongAriProvided = !walletIds.includes(issuerWalletId) | ||
|
||
if (!issuerWalletId || wallets?.length === 0 || wrongAriProvided) { | ||
const walletChoices = | ||
wallets?.map((wallet: WalletDto) => ({ | ||
name: wallet.name || wallet.id, | ||
value: wallet.id, | ||
})) || [] | ||
|
||
const CREATE_NEW_WALLET = 'Create new wallet' | ||
walletChoices.push({ name: CREATE_NEW_WALLET, value: CREATE_NEW_WALLET }) | ||
|
||
const selectedWallet = await select({ | ||
message: 'Select the wallet used for signing credentials that will be issued', | ||
choices: walletChoices, | ||
}) | ||
|
||
if (selectedWallet === CREATE_NEW_WALLET) { | ||
const walletDidMethodChoices = Object.values(DidMethods).map((method: string) => ({ | ||
name: method, | ||
value: method, | ||
default: DidMethods.KEY, | ||
})) | ||
|
||
const name = validateInputLength(await input({ message: 'Enter wallet name' }), INPUT_LIMIT) | ||
const description = validateInputLength( | ||
await input({ message: 'Enter wallet description (optional)' }), | ||
INPUT_LIMIT, | ||
) | ||
const didMethod = await select({ message: 'Select DID method of wallet', choices: walletDidMethodChoices }) | ||
|
||
const isDidWeb = didMethod === DidMethods.WEB | ||
const didWebUrl = isDidWeb | ||
? validateInputLength(await input({ message: 'Enter did:web URL (your applications domain)' }), INPUT_LIMIT) | ||
: undefined | ||
|
||
const newWalletData = { | ||
name, | ||
description, | ||
didMethod, | ||
...(isDidWeb && { didWebUrl }), | ||
} | ||
|
||
const walletSchema = z | ||
.object({ | ||
name: z.string().min(3).max(INPUT_LIMIT).optional(), | ||
description: z.string().max(INPUT_LIMIT).optional(), | ||
didMethod: z.nativeEnum(DidMethods).optional(), | ||
didWebUrl: z.string().min(3).max(INPUT_LIMIT).optional(), | ||
}) | ||
.refine((wallet) => { | ||
if (wallet.didMethod === DidMethods.WEB && !wallet.didWebUrl) { | ||
return false | ||
} | ||
return true | ||
}) | ||
|
||
const createWalletInput = walletSchema.parse(newWalletData) | ||
|
||
ux.action.start('Creating wallet') | ||
const output = await cweService.createWallet(createWalletInput as CreateWalletInput) | ||
ux.action.stop('Created successfully!') | ||
|
||
issuerWalletId = output.wallet?.id || '' | ||
} else { | ||
issuerWalletId = selectedWallet || '' | ||
} | ||
} | ||
|
||
let credentialSupported: CredentialSupportedObject[] | ||
|
||
try { | ||
const rawData = await readFile(validatedFlags.file, 'utf8') | ||
credentialSupported = JSON.parse(rawData) | ||
} catch (error) { | ||
throw new CLIError(`Provided file is not a valid JSON\n${(error as Error).message}`) | ||
} | ||
|
||
const data: CreateIssuanceConfigInput = { | ||
name: | ||
flags.name ?? | ||
validateInputLength(await input({ message: 'Enter credential issuance configuration name' }), INPUT_LIMIT), | ||
description: flags.description ?? '', | ||
issuerWalletId, | ||
credentialOfferDuration: flags['credential-offer-duration'] ?? undefined, | ||
credentialSupported: credentialSupported ?? [], | ||
} | ||
|
||
const credentialSupportedSchema = z.object({ | ||
credentialTypeId: z.string(), | ||
jsonSchemaUrl: z.string(), | ||
jsonLdContextUrl: z.string(), | ||
}) | ||
|
||
const schema = z.object({ | ||
name: z.string().min(3).max(INPUT_LIMIT), | ||
issuerWalletId: z.string().min(1).max(INPUT_LIMIT), | ||
description: z.string().max(INPUT_LIMIT).optional(), | ||
credentialOfferDuration: z.number().min(1).optional(), | ||
credentialSupported: z.array(credentialSupportedSchema), | ||
}) | ||
const configInput = schema.parse(data) | ||
|
||
ux.action.start('Creating credential issuance configuration') | ||
const output = await issuanceService.createIssuanceConfig(configInput) | ||
ux.action.stop('Created successfully!') | ||
|
||
if (!this.jsonEnabled()) this.logJson(output) | ||
return output | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
import { ux, Flags } from '@oclif/core' | ||
import z from 'zod' | ||
import { BaseCommand } from '../../common/base-command.js' | ||
import { promptRequiredParameters } from '../../common/prompts.js' | ||
import { INPUT_LIMIT } from '../../common/validators.js' | ||
import { issuanceService } from '../../services/affinidi/cis/service.js' | ||
|
||
export class DeleteIssuanceConfig extends BaseCommand<typeof DeleteIssuanceConfig> { | ||
static summary = 'Deletes credential issuance configuration from your active project' | ||
static examples = [ | ||
'<%= config.bin %> <%= command.id %> -i <value>', | ||
'<%= config.bin %> <%= command.id %> --id <value>', | ||
] | ||
static flags = { | ||
id: Flags.string({ | ||
char: 'i', | ||
summary: 'ID of the credential issuance configuration', | ||
}), | ||
} | ||
|
||
public async run(): Promise<{ id: string }> { | ||
const { flags } = await this.parse(DeleteIssuanceConfig) | ||
const promptFlags = await promptRequiredParameters(['id'], flags) | ||
|
||
const schema = z.object({ | ||
id: z.string().max(INPUT_LIMIT), | ||
}) | ||
const validatedFlags = schema.parse(promptFlags) | ||
|
||
ux.action.start('Deleting credential issuance configuration') | ||
await issuanceService.deleteIssuanceConfigById(validatedFlags.id) | ||
ux.action.stop('Deleted successfully!') | ||
|
||
if (!this.jsonEnabled()) this.logJson({ id: validatedFlags.id }) | ||
return { id: validatedFlags.id } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { IssuanceConfigDto } from '@affinidi-tdk/credential-issuance-client' | ||
import { ux, Flags } from '@oclif/core' | ||
import z from 'zod' | ||
import { BaseCommand } from '../../common/base-command.js' | ||
import { promptRequiredParameters } from '../../common/prompts.js' | ||
import { INPUT_LIMIT } from '../../common/validators.js' | ||
import { issuanceService } from '../../services/affinidi/cis/service.js' | ||
|
||
export class GetIssuanceConfig extends BaseCommand<typeof GetIssuanceConfig> { | ||
static summary = 'Gets the details of the credential issuance configuration in your active project' | ||
static examples = [ | ||
'<%= config.bin %> <%= command.id %> -i <value>', | ||
'<%= config.bin %> <%= command.id %> --id <value>', | ||
] | ||
static flags = { | ||
id: Flags.string({ | ||
char: 'i', | ||
summary: 'ID of the credential issuance configuration', | ||
}), | ||
} | ||
|
||
public async run(): Promise<IssuanceConfigDto> { | ||
const { flags } = await this.parse(GetIssuanceConfig) | ||
const promptFlags = await promptRequiredParameters(['id'], flags) | ||
|
||
const schema = z.object({ | ||
id: z.string().max(INPUT_LIMIT), | ||
}) | ||
const validatedFlags = schema.parse(promptFlags) | ||
|
||
ux.action.start('Fetching credential issuance configuration') | ||
const output = await issuanceService.getIssuanceConfigById(validatedFlags.id) | ||
ux.action.stop('Fetched successfully!') | ||
|
||
if (!this.jsonEnabled()) this.logJson(output) | ||
return output | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { IssuanceConfigListResponse } from '@affinidi-tdk/credential-issuance-client' | ||
import { ux } from '@oclif/core' | ||
import { BaseCommand } from '../../common/base-command.js' | ||
import { issuanceService } from '../../services/affinidi/cis/service.js' | ||
|
||
export class ListIssuanceConfigs extends BaseCommand<typeof ListIssuanceConfigs> { | ||
static summary = 'Lists credential issuance configurations in your active project' | ||
static examples = ['<%= config.bin %> <%= command.id %>'] | ||
|
||
public async run(): Promise<IssuanceConfigListResponse> { | ||
ux.action.start('Fetching credential issuance configurations') | ||
const output = await issuanceService.listIssuanceConfigs() | ||
ux.action.stop('Fetched successfully!') | ||
|
||
if (!this.jsonEnabled()) this.logJson(output) | ||
return output | ||
} | ||
} |
Oops, something went wrong.