mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2026-02-11 17:32:25 +00:00
132 lines
4.9 KiB
JavaScript
132 lines
4.9 KiB
JavaScript
import { isoTimeToRippleTime, rippleTimeToISOTime, isValidClassicAddress } from 'xrpl'
|
|
import { stringToHex, hexToString } from '@xrplf/isomorphic/dist/utils/index.js'
|
|
|
|
import { ValueError } from './errors.js'
|
|
|
|
// Regex constants
|
|
const CREDENTIAL_REGEX = /^[A-Za-z0-9_.-]{1,128}$/
|
|
const URI_REGEX = /^[A-Za-z0-9\-._~:/?#\[\]@!$&'()*+,;=%]{1,256}$/
|
|
|
|
/**
|
|
* Validate credential request.
|
|
* This function performs parameter validation. Validated fields:
|
|
* - subject (required): the subject of the credential, as a classic address
|
|
* - credential (required): the credential type, in human-readable (ASCII) chars
|
|
* - uri (optional): URI of the credential in human-readable (ASCII) chars
|
|
* - expiration (optional): time when the credential expires (displayed as an ISO 8601 format string in JSON)
|
|
*/
|
|
export function validateCredentialRequest({ subject, credential, uri, expiration }) {
|
|
// Validate subject
|
|
if (typeof subject !== 'string') {
|
|
throw new ValueError("Must provide a string 'subject' field")
|
|
}
|
|
if (!isValidClassicAddress(subject)) {
|
|
throw new ValueError(`subject not valid address: '${subject}'`)
|
|
}
|
|
|
|
// Validate credential
|
|
if (typeof credential !== 'string') {
|
|
throw new ValueError("Must provide a string 'credential' field")
|
|
}
|
|
if (!CREDENTIAL_REGEX.test(credential)) {
|
|
/**
|
|
* Checks if the specified credential type is one that this service issues.
|
|
* XRPL credential types can be any binary data; this service issues
|
|
* any credential that can be encoded from the following ASCII chars:
|
|
* alphanumeric characters, underscore, period, and dash. (min length 1, max 128)
|
|
*
|
|
* You might want to further limit the credential types, depending on your
|
|
* use case; for example, you might only issue one specific credential type.
|
|
*/
|
|
throw new ValueError(`credential not allowed: '${credential}'.`)
|
|
}
|
|
|
|
/*
|
|
(Optional) Checks if the specified URI is acceptable for this service.
|
|
|
|
XRPL Credentials' URI values can be any binary data; this service
|
|
adds any user-requested URI to a Credential as long as the URI
|
|
can be encoded from the characters usually allowed in URIs, namely
|
|
the following ASCII chars:
|
|
|
|
alphanumeric characters (upper and lower case)
|
|
the following symbols: -._~:/?#[]@!$&'()*+,;=%
|
|
(minimum length 1 and max length 256 chars)
|
|
|
|
You might want to instead define your own URI and attach it to the
|
|
Credential regardless of user input, or you might want to verify that the
|
|
URI points to a valid Verifiable Credential document that matches the user.
|
|
*/
|
|
if (uri !== undefined) {
|
|
if (typeof uri !== 'string' || !URI_REGEX.test(uri)) {
|
|
throw new ValueError(`URI isn't valid: ${uri}`)
|
|
}
|
|
}
|
|
|
|
// Validate and parse expiration
|
|
let parsedExpiration
|
|
if (expiration !== undefined) {
|
|
if (typeof expiration !== 'string') {
|
|
throw new ValueError(`Unsupported expiration format: ${typeof expiration}`)
|
|
}
|
|
parsedExpiration = new Date(expiration)
|
|
if (isNaN(parsedExpiration.getTime())) {
|
|
throw new ValueError(`Invalid expiration date: ${expiration}`)
|
|
}
|
|
}
|
|
|
|
return {
|
|
subject,
|
|
credential,
|
|
uri,
|
|
expiration: parsedExpiration,
|
|
}
|
|
}
|
|
|
|
// Convert an XRPL ledger entry into a usable credential object
|
|
export function credentialFromXrpl(entry) {
|
|
const { Subject, CredentialType, URI, Expiration, Flags } = entry
|
|
return {
|
|
subject: Subject,
|
|
credential: hexToString(CredentialType),
|
|
uri: URI ? hexToString(URI) : undefined,
|
|
expiration: Expiration ? rippleTimeToISOTime(Expiration) : undefined,
|
|
accepted: Boolean(Flags & 0x00010000), // lsfAccepted
|
|
}
|
|
}
|
|
|
|
// Convert to an object in a format closer to the XRP Ledger representation
|
|
export function credentialToXrpl(cred) {
|
|
// Credential type and URI are hexadecimal;
|
|
// Expiration, if present, is in seconds since the Ripple Epoch.
|
|
return {
|
|
subject: cred.subject,
|
|
credential: stringToHex(cred.credential),
|
|
uri: cred.uri ? stringToHex(cred.uri) : undefined,
|
|
expiration: cred.expiration ? isoTimeToRippleTime(cred.expiration) : undefined,
|
|
}
|
|
}
|
|
|
|
export function verifyDocuments({ documents }) {
|
|
/**
|
|
* This is where you would check the user's documents to see if you
|
|
* should issue the requested Credential to them.
|
|
* Depending on the type of credentials your service needs, you might
|
|
* need to implement different types of checks here.
|
|
*/
|
|
if (typeof documents !== 'object' || Object.keys(documents).length === 0) {
|
|
throw new ValueError("you must provide a non-empty 'documents' field")
|
|
}
|
|
|
|
// As a placeholder, this example checks that the documents field
|
|
// contains a string field named "reason" containing the word "please".
|
|
const reason = documents.reason
|
|
if (typeof reason !== 'string') {
|
|
throw new ValueError("documents must contain a 'reason' string")
|
|
}
|
|
|
|
if (!reason.toLowerCase().includes('please')) {
|
|
throw new ValueError("reason must include 'please'")
|
|
}
|
|
}
|