mirror of
https://codeberg.org/keyoxide/keyoxide-web.git
synced 2024-12-22 23:09:29 -07:00
fix: fix linting issues
This commit is contained in:
parent
2760adf5f8
commit
b8114bad9e
4 changed files with 256 additions and 278 deletions
|
@ -157,7 +157,7 @@ router.get('/verify',
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let profile = Claim.fromJson(req.query.data)
|
const profile = Claim.fromJson(req.query.data)
|
||||||
|
|
||||||
// Do verification
|
// Do verification
|
||||||
let data = await doVerification(profile)
|
let data = await doVerification(profile)
|
||||||
|
|
507
src/schemas.js
507
src/schemas.js
|
@ -28,336 +28,335 @@ if any, to sign a "copyright disclaimer" for the program, if necessary. For
|
||||||
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
more information on this, and how to apply and follow the GNU AGPL, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
export const profileSchema = {
|
export const profileSchema = {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||||
"$id": "https://spec.keyoxide.org/2/profile.schema.json",
|
$id: 'https://spec.keyoxide.org/2/profile.schema.json',
|
||||||
"title": "Profile",
|
title: 'Profile',
|
||||||
"description": "Keyoxide profile with personas",
|
description: 'Keyoxide profile with personas',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"profileVersion": {
|
profileVersion: {
|
||||||
"description": "The version of the profile",
|
description: 'The version of the profile',
|
||||||
"type": "integer"
|
type: 'integer'
|
||||||
},
|
},
|
||||||
"profileType": {
|
profileType: {
|
||||||
"description": "The type of the profile [openpgp, asp]",
|
description: 'The type of the profile [openpgp, asp]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"identifier": {
|
identifier: {
|
||||||
"description": "Identifier of the profile (email, fingerprint, URI)",
|
description: 'Identifier of the profile (email, fingerprint, URI)',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"personas": {
|
personas: {
|
||||||
"description": "The personas inside the profile",
|
description: 'The personas inside the profile',
|
||||||
"type": "array",
|
type: 'array',
|
||||||
"items": {
|
items: {
|
||||||
"$ref": "https://spec.keyoxide.org/2/persona.schema.json"
|
$ref: 'https://spec.keyoxide.org/2/persona.schema.json'
|
||||||
},
|
},
|
||||||
"minItems": 1,
|
minItems: 1,
|
||||||
"uniqueItems": true
|
uniqueItems: true
|
||||||
},
|
},
|
||||||
"primaryPersonaIndex": {
|
primaryPersonaIndex: {
|
||||||
"description": "The index of the primary persona",
|
description: 'The index of the primary persona',
|
||||||
"type": "integer"
|
type: 'integer'
|
||||||
},
|
},
|
||||||
"publicKey": {
|
publicKey: {
|
||||||
"description": "The cryptographic key associated with the profile",
|
description: 'The cryptographic key associated with the profile',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"keyType": {
|
keyType: {
|
||||||
"description": "The type of cryptographic key [eddsa, es256, openpgp, none]",
|
description: 'The type of cryptographic key [eddsa, es256, openpgp, none]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"encoding": {
|
encoding: {
|
||||||
"description": "The encoding of the cryptographic key [pem, jwk, armored_pgp, none]",
|
description: 'The encoding of the cryptographic key [pem, jwk, armored_pgp, none]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"encodedKey": {
|
encodedKey: {
|
||||||
"description": "The encoded cryptographic key (PEM, stringified JWK, ...)",
|
description: 'The encoded cryptographic key (PEM, stringified JWK, ...)',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
},
|
},
|
||||||
"fetch": {
|
fetch: {
|
||||||
"description": "Details on how to fetch the public key",
|
description: 'Details on how to fetch the public key',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"method": {
|
method: {
|
||||||
"description": "The method to fetch the key [aspe, hkp, wkd, http, none]",
|
description: 'The method to fetch the key [aspe, hkp, wkd, http, none]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"query": {
|
query: {
|
||||||
"description": "The query to fetch the key",
|
description: 'The query to fetch the key',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
},
|
},
|
||||||
"resolvedUrl": {
|
resolvedUrl: {
|
||||||
"description": "The URL the method eventually resolved to",
|
description: 'The URL the method eventually resolved to',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
required: [
|
||||||
"keyType",
|
'keyType',
|
||||||
"fetch"
|
'fetch'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"verifiers": {
|
verifiers: {
|
||||||
"description": "A list of links to verifiers",
|
description: 'A list of links to verifiers',
|
||||||
"type": "array",
|
type: 'array',
|
||||||
"items": {
|
items: {
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"name": {
|
name: {
|
||||||
"description": "Name of the verifier site",
|
description: 'Name of the verifier site',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"url": {
|
url: {
|
||||||
"description": "URL to the profile page on the verifier site",
|
description: 'URL to the profile page on the verifier site',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"uniqueItems": true
|
uniqueItems: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
required: [
|
||||||
"profileVersion",
|
'profileVersion',
|
||||||
"profileType",
|
'profileType',
|
||||||
"identifier",
|
'identifier',
|
||||||
"personas",
|
'personas',
|
||||||
"primaryPersonaIndex",
|
'primaryPersonaIndex',
|
||||||
"publicKey",
|
'publicKey',
|
||||||
"verifiers"
|
'verifiers'
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
additionalProperties: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const personaSchema = {
|
export const personaSchema = {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||||
"$id": "https://spec.keyoxide.org/2/persona.schema.json",
|
$id: 'https://spec.keyoxide.org/2/persona.schema.json',
|
||||||
"title": "Profile",
|
title: 'Profile',
|
||||||
"description": "Keyoxide persona with identity claims",
|
description: 'Keyoxide persona with identity claims',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"identifier": {
|
identifier: {
|
||||||
"description": "Identifier of the persona",
|
description: 'Identifier of the persona',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
},
|
},
|
||||||
"name": {
|
name: {
|
||||||
"description": "Name of the persona",
|
description: 'Name of the persona',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"email": {
|
email: {
|
||||||
"description": "Email address of the persona",
|
description: 'Email address of the persona',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
},
|
},
|
||||||
"description": {
|
description: {
|
||||||
"description": "Description of the persona",
|
description: 'Description of the persona',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
},
|
},
|
||||||
"avatarUrl": {
|
avatarUrl: {
|
||||||
"description": "URL to an avatar image",
|
description: 'URL to an avatar image',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
},
|
},
|
||||||
"isRevoked": {
|
isRevoked: {
|
||||||
"type": "boolean"
|
type: 'boolean'
|
||||||
},
|
},
|
||||||
"claims": {
|
claims: {
|
||||||
"description": "A list of identity claims",
|
description: 'A list of identity claims',
|
||||||
"type": "array",
|
type: 'array',
|
||||||
"items": {
|
items: {
|
||||||
"$ref": "https://spec.keyoxide.org/2/claim.schema.json"
|
$ref: 'https://spec.keyoxide.org/2/claim.schema.json'
|
||||||
},
|
},
|
||||||
"uniqueItems": true
|
uniqueItems: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
required: [
|
||||||
"name",
|
'name',
|
||||||
"claims"
|
'claims'
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
additionalProperties: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const claimSchema = {
|
export const claimSchema = {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||||
"$id": "https://spec.keyoxide.org/2/claim.schema.json",
|
$id: 'https://spec.keyoxide.org/2/claim.schema.json',
|
||||||
"title": "Identity claim",
|
title: 'Identity claim',
|
||||||
"description": "Verifiable online identity claim",
|
description: 'Verifiable online identity claim',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"claimVersion": {
|
claimVersion: {
|
||||||
"description": "The version of the claim",
|
description: 'The version of the claim',
|
||||||
"type": "integer"
|
type: 'integer'
|
||||||
},
|
},
|
||||||
"uri": {
|
uri: {
|
||||||
"description": "The claim URI",
|
description: 'The claim URI',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"proofs": {
|
proofs: {
|
||||||
"description": "The proofs that would verify the claim",
|
description: 'The proofs that would verify the claim',
|
||||||
"type": "array",
|
type: 'array',
|
||||||
"items": {
|
items: {
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"minItems": 1,
|
minItems: 1,
|
||||||
"uniqueItems": true
|
uniqueItems: true
|
||||||
},
|
},
|
||||||
"matches": {
|
matches: {
|
||||||
"description": "Service providers matched to the claim",
|
description: 'Service providers matched to the claim',
|
||||||
"type": "array",
|
type: 'array',
|
||||||
"items": {
|
items: {
|
||||||
"$ref": "https://spec.keyoxide.org/2/serviceprovider.schema.json"
|
$ref: 'https://spec.keyoxide.org/2/serviceprovider.schema.json'
|
||||||
},
|
},
|
||||||
"uniqueItems": true
|
uniqueItems: true
|
||||||
},
|
},
|
||||||
"status": {
|
status: {
|
||||||
"type": "integer",
|
type: 'integer',
|
||||||
"description": "Claim status code"
|
description: 'Claim status code'
|
||||||
},
|
},
|
||||||
"display": {
|
display: {
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"name": {
|
name: {
|
||||||
"type": "string",
|
type: 'string',
|
||||||
"description": "Account name to display in the user interface"
|
description: 'Account name to display in the user interface'
|
||||||
},
|
},
|
||||||
"url": {
|
url: {
|
||||||
"type": ["string", "null"],
|
type: ['string', 'null'],
|
||||||
"description": "URL to link to in the user interface"
|
description: 'URL to link to in the user interface'
|
||||||
},
|
},
|
||||||
"serviceProviderName": {
|
serviceProviderName: {
|
||||||
"type": ["string", "null"],
|
type: ['string', 'null'],
|
||||||
"description": "Name of the service provider to display in the user interface"
|
description: 'Name of the service provider to display in the user interface'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
required: [
|
||||||
"claimVersion",
|
'claimVersion',
|
||||||
"uri",
|
'uri',
|
||||||
"proofs",
|
'proofs',
|
||||||
"status",
|
'status',
|
||||||
"display"
|
'display'
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
additionalProperties: false
|
||||||
}
|
}
|
||||||
|
|
||||||
export const serviceProviderSchema = {
|
export const serviceProviderSchema = {
|
||||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||||
"$id": "https://spec.keyoxide.org/2/serviceprovider.schema.json",
|
$id: 'https://spec.keyoxide.org/2/serviceprovider.schema.json',
|
||||||
"title": "Service provider",
|
title: 'Service provider',
|
||||||
"description": "A service provider that can be matched to identity claims",
|
description: 'A service provider that can be matched to identity claims',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"about": {
|
about: {
|
||||||
"description": "Details about the service provider",
|
description: 'Details about the service provider',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"name": {
|
name: {
|
||||||
"description": "Full name of the service provider",
|
description: 'Full name of the service provider',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"id": {
|
id: {
|
||||||
"description": "Identifier of the service provider (no whitespace or symbols, lowercase)",
|
description: 'Identifier of the service provider (no whitespace or symbols, lowercase)',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"homepage": {
|
homepage: {
|
||||||
"description": "URL to the homepage of the service provider",
|
description: 'URL to the homepage of the service provider',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"profile": {
|
profile: {
|
||||||
"description": "What the profile would look like if the match is correct",
|
description: 'What the profile would look like if the match is correct',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"display": {
|
display: {
|
||||||
"description": "Profile name to be displayed",
|
description: 'Profile name to be displayed',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"uri": {
|
uri: {
|
||||||
"description": "URI or URL for public access to the profile",
|
description: 'URI or URL for public access to the profile',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"qr": {
|
qr: {
|
||||||
"description": "URI or URL associated with the profile usually served as a QR code",
|
description: 'URI or URL associated with the profile usually served as a QR code',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"claim": {
|
claim: {
|
||||||
"description": "Details from the claim matching process",
|
description: 'Details from the claim matching process',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"uriRegularExpression": {
|
uriRegularExpression: {
|
||||||
"description": "Regular expression used to parse the URI",
|
description: 'Regular expression used to parse the URI',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"uriIsAmbiguous": {
|
uriIsAmbiguous: {
|
||||||
"description": "Whether this match automatically excludes other matches",
|
description: 'Whether this match automatically excludes other matches',
|
||||||
"type": "boolean"
|
type: 'boolean'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"proof": {
|
proof: {
|
||||||
"description": "Information for the proof verification process",
|
description: 'Information for the proof verification process',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"request": {
|
request: {
|
||||||
"description": "Details to request the potential proof",
|
description: 'Details to request the potential proof',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"uri": {
|
uri: {
|
||||||
"description": "Location of the proof",
|
description: 'Location of the proof',
|
||||||
"type": ["string", "null"]
|
type: ['string', 'null']
|
||||||
},
|
},
|
||||||
"accessRestriction": {
|
accessRestriction: {
|
||||||
"description": "Type of access restriction [none, nocors, granted, server]",
|
description: 'Type of access restriction [none, nocors, granted, server]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"fetcher": {
|
fetcher: {
|
||||||
"description": "Name of the fetcher to use",
|
description: 'Name of the fetcher to use',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"data": {
|
data: {
|
||||||
"description": "Data needed by the fetcher or proxy to request the proof",
|
description: 'Data needed by the fetcher or proxy to request the proof',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"additionalProperties": true
|
additionalProperties: true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"response": {
|
response: {
|
||||||
"description": "Details about the expected response",
|
description: 'Details about the expected response',
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"format": {
|
format: {
|
||||||
"description": "Expected format of the proof [text, json]",
|
description: 'Expected format of the proof [text, json]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"target": {
|
target: {
|
||||||
"description": "Details about the target located in the response",
|
description: 'Details about the target located in the response',
|
||||||
"type": "array",
|
type: 'array',
|
||||||
"items": {
|
items: {
|
||||||
"type": "object",
|
type: 'object',
|
||||||
"properties": {
|
properties: {
|
||||||
"format": {
|
format: {
|
||||||
"description": "How is the proof formatted [uri, fingerprint]",
|
description: 'How is the proof formatted [uri, fingerprint]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"encoding": {
|
encoding: {
|
||||||
"description": "How is the proof encoded [plain, html, xml]",
|
description: 'How is the proof encoded [plain, html, xml]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"relation": {
|
relation: {
|
||||||
"description": "How are the response and the target related [contains, equals]",
|
description: 'How are the response and the target related [contains, equals]',
|
||||||
"type": "string"
|
type: 'string'
|
||||||
},
|
},
|
||||||
"path": {
|
path: {
|
||||||
"description": "Path to the target location if the response is JSON",
|
description: 'Path to the target location if the response is JSON',
|
||||||
"type": "array",
|
type: 'array',
|
||||||
"items": {
|
items: {
|
||||||
"type": "string"
|
type: 'string'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -366,11 +365,11 @@ export const serviceProviderSchema = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
required: [
|
||||||
"about",
|
'about',
|
||||||
"profile",
|
'profile',
|
||||||
"claim",
|
'claim',
|
||||||
"proof"
|
'proof'
|
||||||
],
|
],
|
||||||
"additionalProperties": false
|
additionalProperties: false
|
||||||
}
|
}
|
||||||
|
|
|
@ -145,7 +145,6 @@ const generateSignatureProfile = async (signature) => {
|
||||||
return fetchSignature(signature)
|
return fetchSignature(signature)
|
||||||
.then(async key => {
|
.then(async key => {
|
||||||
let profile = await doipjs.signatures.parse(key.publicKey)
|
let profile = await doipjs.signatures.parse(key.publicKey)
|
||||||
profile.addVerifier('keyoxide', keyoxideUrl)
|
|
||||||
profile = processOpenPgpProfile(profile)
|
profile = processOpenPgpProfile(profile)
|
||||||
|
|
||||||
logger.debug('Generating a signature profile',
|
logger.debug('Generating a signature profile',
|
||||||
|
@ -255,16 +254,6 @@ const processOpenPgpProfile = async (/** @type {import('doipjs').Profile */ prof
|
||||||
return profile
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
const computeExtraData = async (key, keyData) => {
|
|
||||||
// Get the primary user
|
|
||||||
const primaryUser = await key.publicKey.getPrimaryUser()
|
|
||||||
|
|
||||||
// Query libravatar to get the avatar url
|
|
||||||
return {
|
|
||||||
avatarURL: await libravatar.get_avatar_url({ email: primaryUser.user.userID.email, size: 128, default: 'mm', https: true })
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { generateAspeProfile }
|
export { generateAspeProfile }
|
||||||
export { generateWKDProfile }
|
export { generateWKDProfile }
|
||||||
export { generateHKPProfile }
|
export { generateHKPProfile }
|
||||||
|
|
|
@ -29,7 +29,7 @@ more information on this, and how to apply and follow the GNU AGPL, see <https:/
|
||||||
*/
|
*/
|
||||||
import got from 'got'
|
import got from 'got'
|
||||||
import * as doipjs from 'doipjs'
|
import * as doipjs from 'doipjs'
|
||||||
import { readKey, readCleartextMessage, verify, PublicKey } from 'openpgp'
|
import { readKey } from 'openpgp'
|
||||||
import { computeWKDLocalPart } from './utils.js'
|
import { computeWKDLocalPart } from './utils.js'
|
||||||
import { createHash } from 'crypto'
|
import { createHash } from 'crypto'
|
||||||
import Keyv from 'keyv'
|
import Keyv from 'keyv'
|
||||||
|
@ -176,16 +176,6 @@ const fetchSignature = (signature) => {
|
||||||
(async () => {
|
(async () => {
|
||||||
let profile = null
|
let profile = null
|
||||||
|
|
||||||
// Check validity of signature
|
|
||||||
let signatureData
|
|
||||||
try {
|
|
||||||
signatureData = await readCleartextMessage({
|
|
||||||
cleartextMessage: signature
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
reject(new Error(`Signature could not be properly read (${error.message})`))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process the signature
|
// Process the signature
|
||||||
try {
|
try {
|
||||||
profile = await doipjs.signatures.parse(signature)
|
profile = await doipjs.signatures.parse(signature)
|
||||||
|
|
Loading…
Reference in a new issue