mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-21 04:05:49 +00:00
139 lines
4.9 KiB
JavaScript
139 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'");
|
|
}
|
|
}
|