forked from Mirrors/doipjs
Compare commits
No commits in common. "dev" and "into-es-module" have entirely different histories.
dev
...
into-es-mo
95 changed files with 20909 additions and 73426 deletions
|
@ -4,10 +4,7 @@
|
|||
"es2021": true,
|
||||
"node": true
|
||||
},
|
||||
"extends": [
|
||||
"standard",
|
||||
"plugin:jsdoc/recommended"
|
||||
],
|
||||
"extends": "standard",
|
||||
"overrides": [
|
||||
],
|
||||
"parserOptions": {
|
||||
|
@ -15,8 +12,5 @@
|
|||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
},
|
||||
"plugins": [
|
||||
"jsdoc"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
---
|
||||
name: 'Bug'
|
||||
about: 'Report a bug'
|
||||
title: '[BUG] '
|
||||
ref: 'dev'
|
||||
labels:
|
||||
- 'Status/Needs Triage'
|
||||
- Type/Bug
|
||||
---
|
||||
|
||||
### What happened
|
||||
|
||||
|
||||
|
||||
### Proposed solutions
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
---
|
||||
name: 'Claim verification bug'
|
||||
about: 'Report a claim no longer verifying, or not verifying as it should'
|
||||
title: '[CLAIM BUG] '
|
||||
ref: 'dev'
|
||||
labels:
|
||||
- 'Status/Needs Triage'
|
||||
- Type/Bug
|
||||
---
|
||||
|
||||
### Service provider
|
||||
|
||||
Name:
|
||||
|
||||
### Profile with the bug
|
||||
|
||||
<!-- Optional: only if you're willing to share your profile -->
|
||||
Link to profile:
|
||||
|
||||
### What happened
|
||||
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
---
|
||||
name: 'New claim'
|
||||
about: 'Suggest a new service provider or website for identity verification'
|
||||
title: '[NEW CLAIM] '
|
||||
ref: 'dev'
|
||||
labels:
|
||||
- 'Status/Needs Triage'
|
||||
- 'Type/New Claim'
|
||||
---
|
||||
|
||||
### Service provider
|
||||
|
||||
Name:
|
||||
|
||||
Short description:
|
||||
|
||||
Website:
|
||||
|
||||
API documentation:
|
||||
|
||||
### Proposed verification mechanism
|
||||
|
||||
<!-- Optional, only fill in if you already know which APIs to use, etc -->
|
||||
|
||||
|
||||
### Remarks
|
||||
|
||||
|
||||
|
||||
### Tasks
|
||||
|
||||
<!-- Leave the following unchecked -->
|
||||
- [ ] Verification mechanism tested
|
||||
- [ ] Added to [doip-js](https://codeberg.org/keyoxide/doipjs)
|
||||
- [ ] Added to [doip-rs](https://codeberg.org/keyoxide/doip-rs)
|
||||
- [ ] Added proxy routes (if needed)
|
||||
- [ ] Added to [keyoxide-brands](https://codeberg.org/keyoxide/keyoxide-brands)
|
||||
- [ ] Added to [documentation](https://codeberg.org/keyoxide/keyoxide-docs)
|
|
@ -1,8 +1,8 @@
|
|||
when:
|
||||
branch: main
|
||||
event: tag
|
||||
steps:
|
||||
pipeline:
|
||||
prepare:
|
||||
when:
|
||||
branch: main
|
||||
event: tag
|
||||
image: node
|
||||
commands:
|
||||
- yarn --pure-lockfile
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
steps:
|
||||
pipeline:
|
||||
test:
|
||||
image: node
|
||||
commands:
|
||||
|
|
Binary file not shown.
|
@ -1,6 +0,0 @@
|
|||
nodeLinker: node-modules
|
||||
npmScopes:
|
||||
myriation:
|
||||
npmPublishRegistry: https://git.myriation.xyz/api/packages/myriation/npm/
|
||||
npmAlwaysAuth: true
|
||||
npmAuthToken: REPLACE-ME
|
86
CHANGELOG.md
86
CHANGELOG.md
|
@ -6,92 +6,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.2.9] - 2024-02-01
|
||||
### Added
|
||||
- ORCiD identity claims
|
||||
### Changed
|
||||
- Improved code documentation
|
||||
- Optimized creation of Regexp instances
|
||||
### Fixed
|
||||
- Bad promise timeout logic
|
||||
- Dependencies cleaned up
|
||||
|
||||
## [1.2.8] - 2024-01-23
|
||||
### Added
|
||||
- OpenPGP and ASP claims
|
||||
|
||||
## [1.2.7] - 2023-10-09
|
||||
### Fixed
|
||||
- Fix regex errors
|
||||
|
||||
## [1.2.6] - 2023-10-09
|
||||
### Added
|
||||
- JSON schemas for common objects
|
||||
### Changed
|
||||
- Additional Github proof location (proof.md)
|
||||
### Fixed
|
||||
- IRC compatibility with ASP profiles
|
||||
- IRC profile display value
|
||||
- Lobste.rs profile URL value
|
||||
|
||||
## [1.2.5] - 2023-10-05
|
||||
### Added
|
||||
- Support for theme color
|
||||
|
||||
## [1.2.4] - 2023-10-04
|
||||
### Changed
|
||||
- Claim display information
|
||||
|
||||
## [1.2.3] - 2023-10-03
|
||||
### Fixed
|
||||
- Claim ambiguity logic
|
||||
|
||||
## [1.2.2] - 2023-10-03
|
||||
### Fixed
|
||||
- Service provider information for Lichess and Keybase
|
||||
- Display data logic in claim toJSON
|
||||
|
||||
## [1.2.1] - 2023-09-23
|
||||
Bump necessary due to tag-related glitch in git forge
|
||||
|
||||
## [1.2.0] - 2023-09-23
|
||||
### Added
|
||||
- Allow service providers to validate the claim verification result (useful for forks)
|
||||
- Support for Forgejo claims
|
||||
|
||||
## [1.1.1] - 2023-09-22
|
||||
### Fixed
|
||||
- Normalize case before hashed proof verification
|
||||
|
||||
## [1.1.0] - 2023-09-21
|
||||
### Changed
|
||||
- Unify fromJSON() for Profile, Persona and Claim classes
|
||||
|
||||
## [1.0.4] - 2023-09-19
|
||||
### Fixed
|
||||
- Allow the activitypub Person request to fail
|
||||
|
||||
## [1.0.3] - 2023-09-19
|
||||
### Fixed
|
||||
- Avoid using potentially missing URL for ActivityPub postprocessing
|
||||
|
||||
## [1.0.2] - 2023-09-19
|
||||
### Fixed
|
||||
- Make nodeinfo requests use HTTPS
|
||||
|
||||
## [1.0.1] - 2023-09-18
|
||||
### Fixed
|
||||
- Ignore OpenPGP users without userId
|
||||
- OpenCollective GraphQL queries
|
||||
- Improve ActivityPub post proofs support
|
||||
|
||||
## [1.0.0] - 2023-07-13
|
||||
### Changed
|
||||
- Moved from CommonJS to ESM
|
||||
- All profiles now use the Profile class
|
||||
- Functions that used to return OpenPGP keys now return Profile objects
|
||||
- Compliance with https://spec.keyoxide.org/spec/2/
|
||||
|
||||
## [0.19.0] - 2023-07-04
|
||||
### Added
|
||||
- Support for ASPE protocol
|
||||
|
|
58
README.md
58
README.md
|
@ -1,18 +1,22 @@
|
|||
# doip.js
|
||||
|
||||
[![status-badge](https://ci.codeberg.org/api/badges/5907/status.svg)](https://ci.codeberg.org/repos/5907)
|
||||
[![License](https://img.shields.io/badge/license-Apache--2.0-blue?style=flat)](https://codeberg.org/keyoxide/doipjs/src/branch/main/LICENSE)
|
||||
[![Mastodon Follow](https://img.shields.io/mastodon/follow/247838?domain=https%3A%2F%2Ffosstodon.org&style=flat)](https://fosstodon.org/@keyoxide)
|
||||
[![Open Collective backers and sponsors](https://img.shields.io/opencollective/all/keyoxide?style=flat)](https://opencollective.com/keyoxide)
|
||||
|
||||
![](static/doip.png)
|
||||
![](doip.png)
|
||||
|
||||
[doip.js](https://codeberg.org/keyoxide/doipjs) allows websites and Node.js projects to verify decentralized online
|
||||
identities.
|
||||
doip.js allows websites and Node.js projects to verify decentralized online
|
||||
identities based on OpenPGP.
|
||||
|
||||
Source code available at [codeberg.org](https://codeberg.org/keyoxide/doipjs).
|
||||
|
||||
Documentation available at [js.doip.rocks](https://js.doip.rocks).
|
||||
|
||||
## Features
|
||||
|
||||
- Verify online identities using decentralized technology
|
||||
- Based on [OpenPGP](https://www.openpgp.org), a widely-used cryptographic standard
|
||||
- Regex-based service provider detection
|
||||
- [Mocha](https://mochajs.org) tests
|
||||
|
||||
## Installation (node)
|
||||
|
||||
Install using **yarn** or **npm**:
|
||||
|
@ -52,32 +56,32 @@ const verifyIdentity = async (url, fp) => {
|
|||
verifyIdentity('dns:doip.rocks', '9f0048ac0b23301e1f77e994909f6bd6f80f485d')
|
||||
```
|
||||
|
||||
This snippet verifies the [doip.rocks](https://doip.rocks) domain as
|
||||
This snippet works and will verify the [doip.rocks](https://doip.rocks) domain as
|
||||
bidirectionally linked to Yarmo's cryptographic key.
|
||||
|
||||
## Contributing
|
||||
## About Keyoxide
|
||||
|
||||
Anyone can contribute!
|
||||
[Keyoxide](https://keyoxide.org/), made by Yarmo Mackenbach, is a modern, secure
|
||||
and privacy-friendly platform to establish decentralized online identities using
|
||||
a novel concept know as [DOIP](doip.md). In an effort to make this technology
|
||||
accessible for other projects and stimulate the emergence of both complementary
|
||||
and competing projects, this project-agnostic library is
|
||||
[published on codeberg.org](https://codeberg.org/keyoxide/doipjs) and open
|
||||
sourced under the
|
||||
[Apache-2.0](https://codeberg.org/keyoxide/doipjs/src/branch/main/LICENSE)
|
||||
license.
|
||||
|
||||
Developers are invited to:
|
||||
## Community
|
||||
|
||||
- fork the repository and play around
|
||||
- submit PRs to [implement new features or fix bugs](https://codeberg.org/keyoxide/doipjs/issues)
|
||||
There's a [Keyoxide Matrix room](https://matrix.to/#/#keyoxide:matrix.org) where
|
||||
we discuss everything DOIP and Keyoxide.
|
||||
|
||||
If you are new to contributing to open source software, we'd love to help you! To get started, here's a [list of "good first issues"](https://codeberg.org/keyoxide/doipjs/issues?q=&type=all&state=open&labels=183598) that you could look into.
|
||||
## Donate
|
||||
|
||||
Everyone is invited to:
|
||||
Please consider [donating](https://liberapay.com/Keyoxide/) if you think this
|
||||
project is a step in the right direction for the internet.
|
||||
|
||||
- find and [report bugs](https://codeberg.org/keyoxide/doipjs/issues/new/choose)
|
||||
- suggesting [new features](https://codeberg.org/keyoxide/doipjs/issues/new/choose)
|
||||
- [help with translations](https://translate.codeberg.org/projects/keyoxide/)
|
||||
- [improve documentation](https://codeberg.org/keyoxide/keyoxide-docs)
|
||||
- start using open source software and promote it
|
||||
## Funding
|
||||
|
||||
Please note that this project has a [Code of Conduct](https://codeberg.org/keyoxide/web/src/branch/main/CODE_OF_CONDUCT.md) that all contributors agree to abide when participating.
|
||||
|
||||
## About the Keyoxide project
|
||||
|
||||
The Keyoxide project strives for a healthier internet for all and has made its efforts fully [open source](https://codeberg.org/keyoxide). Our [community](https://docs.keyoxide.org/community/) is open and welcoming, feel free to say hi!
|
||||
|
||||
Funding for the project comes from the [NLnet foundation](https://nlnet.nl/), [NGI0](https://www.ngi.eu/) and the people supporting our [OpenCollective](https://opencollective.com/keyoxide). The project is grateful for all your support.
|
||||
This library was realized with funding from
|
||||
[NLnet](https://nlnet.nl/project/Keyoxide/).
|
||||
|
|
23530
dist/doip.core.js
vendored
23530
dist/doip.core.js
vendored
File diff suppressed because one or more lines are too long
24
dist/doip.core.min.js
vendored
24
dist/doip.core.min.js
vendored
File diff suppressed because one or more lines are too long
14297
dist/doip.fetchers.js
vendored
14297
dist/doip.fetchers.js
vendored
File diff suppressed because one or more lines are too long
22
dist/doip.fetchers.min.js
vendored
22
dist/doip.fetchers.min.js
vendored
File diff suppressed because one or more lines are too long
39985
dist/doip.fetchers.minimal.js
vendored
39985
dist/doip.fetchers.minimal.js
vendored
File diff suppressed because one or more lines are too long
22
dist/doip.fetchers.minimal.min.js
vendored
22
dist/doip.fetchers.minimal.min.js
vendored
File diff suppressed because one or more lines are too long
23
examples/fetch-key-hkp.js
Normal file
23
examples/fetch-key-hkp.js
Normal file
|
@ -0,0 +1,23 @@
|
|||
import * as doip from '../src/index.js'
|
||||
|
||||
const main = async () => {
|
||||
// Fetch the key using HKP
|
||||
const key = await doip.keys.fetchHKP("test@doip.rocks")
|
||||
|
||||
// Process it to extract the UIDs and their claims
|
||||
const obj = await doip.keys.process(key)
|
||||
|
||||
// Process every claim for every user
|
||||
obj.users.forEach(async user => {
|
||||
user.claims.forEach(async claim => {
|
||||
// Match the claim
|
||||
await claim.match()
|
||||
|
||||
// Verify the claim
|
||||
await claim.verify()
|
||||
console.log(claim)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
|
@ -28,11 +28,14 @@ fCRSXrr7SZxIu7I8jfQrxc0k9XhpPI/gdlgRqoEG2lMyqFaWzyoI9dyoVwji78rg
|
|||
=Csr+
|
||||
-----END PGP PUBLIC KEY BLOCK-----`
|
||||
|
||||
// Use the plaintext key to get a profile
|
||||
const profile = await doip.openpgp.fetchPlaintext(pubKeyPlaintext)
|
||||
// Fetch the key using WKD
|
||||
const key = await doip.keys.fetchPlaintext(pubKeyPlaintext)
|
||||
|
||||
// Process it to extract the UIDs and their claims
|
||||
const obj = await doip.keys.process(key)
|
||||
|
||||
// Log the claims of the first UID
|
||||
console.log(profile.personas[0].claims)
|
||||
console.log(obj.users[0].claims)
|
||||
}
|
||||
|
||||
main()
|
14
examples/fetch-key-wkd.js
Normal file
14
examples/fetch-key-wkd.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
import * as doip from '../src/index.js'
|
||||
|
||||
const main = async () => {
|
||||
// Fetch the key using WKD
|
||||
const key = await doip.keys.fetchWKD("test@doip.rocks")
|
||||
|
||||
// Process it to extract the UIDs and their claims
|
||||
const obj = await doip.keys.process(key)
|
||||
|
||||
// Log the claims of the first UID
|
||||
console.log(obj.users[0].claims)
|
||||
}
|
||||
|
||||
main()
|
|
@ -3,6 +3,7 @@ import * as doip from '../src/index.js'
|
|||
const main = async () => {
|
||||
// Fetch the profile using ASPE
|
||||
const profile = await doip.asp.fetchASPE("aspe:keyoxide.org:6WJK26YKF6WUVPIZTS2I2BIT64")
|
||||
console.log(profile);
|
||||
|
||||
// Process every claim for every persona
|
||||
profile.personas[0].claims.forEach(async claim => {
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
import * as doip from '../src/index.js'
|
||||
|
||||
const main = async () => {
|
||||
// Fetch the profile using HKP
|
||||
const profile = await doip.openpgp.fetchHKP("test@doip.rocks")
|
||||
|
||||
// Process every claim for every persona
|
||||
profile.personas.forEach(async persona => {
|
||||
persona.claims.forEach(async claim => {
|
||||
// Match the claim
|
||||
await claim.match()
|
||||
|
||||
// Verify the claim
|
||||
await claim.verify()
|
||||
console.log(claim)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
main()
|
|
@ -1,11 +0,0 @@
|
|||
import * as doip from '../src/index.js'
|
||||
|
||||
const main = async () => {
|
||||
// Fetch the profile using WKD
|
||||
const profile = await doip.openpgp.fetchWKD("test@doip.rocks")
|
||||
|
||||
// Log the claims of the first persona
|
||||
console.log(profile.personas[0].claims)
|
||||
}
|
||||
|
||||
main()
|
|
@ -25,10 +25,10 @@ cXbjvHSGniZ7M3S9S8knAfIquPvTp7+L7wWgSSB5VObPp1r+96n87hyFZUp7PCvl
|
|||
|
||||
const main = async () => {
|
||||
// Process the OpenPGP signature
|
||||
const profile = await doip.signatures.process(signature)
|
||||
const sigProfile = await doip.signatures.process(signature)
|
||||
|
||||
// Log the claims of the first persona
|
||||
console.log(profile.users[0].claims)
|
||||
// Log the processed signature profile
|
||||
console.log(sigProfile.users[0].claims)
|
||||
}
|
||||
|
||||
main()
|
|
@ -1,9 +0,0 @@
|
|||
import * as doip from '../src/index.js'
|
||||
|
||||
const main = async () => {
|
||||
// const sp = doip.ServiceProviderDefinitions.data['activitypub'].processURI('https://fosstodon.org/@yarmo')
|
||||
const sp = doip.ServiceProviderDefinitions.data['discourse'].processURI('https://domain.org/u/alice')
|
||||
console.log(sp);
|
||||
}
|
||||
|
||||
main()
|
|
@ -1,8 +1,5 @@
|
|||
{
|
||||
"plugins": [
|
||||
"plugins/markdown",
|
||||
"node_modules/jsdoc-tsimport-plugin"
|
||||
],
|
||||
"plugins": ["plugins/markdown"],
|
||||
"source": {
|
||||
"include": ["./src", "./README.md"]
|
||||
},
|
||||
|
@ -15,26 +12,21 @@
|
|||
}
|
||||
},
|
||||
"opts": {
|
||||
"template": "node_modules/docdash",
|
||||
"destination": "docs/"
|
||||
},
|
||||
"docdash": {
|
||||
"collapse": true,
|
||||
"meta": {
|
||||
"title": "doipjs",
|
||||
"description": "Documentation for the doip.js library"
|
||||
},
|
||||
"menu": {
|
||||
"Keyoxide": {
|
||||
"href":"https://keyoxide.org",
|
||||
"target":"_blank",
|
||||
"class":"menu-item"
|
||||
},
|
||||
"Keyoxide docs": {
|
||||
"href":"https://docs.keyoxide.org",
|
||||
"target":"_blank",
|
||||
"class":"menu-item"
|
||||
}
|
||||
"template": "node_modules/clean-jsdoc-theme",
|
||||
"theme_opts": {
|
||||
"theme": "light",
|
||||
"menu": [
|
||||
{
|
||||
"title": "Source code",
|
||||
"link": "https://codeberg.org/keyoxide/doipjs",
|
||||
"target": "_blank"
|
||||
},
|
||||
{
|
||||
"title": "Keyoxide",
|
||||
"link": "https://keyoxide.org",
|
||||
"target": "_blank"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
35
package.json
35
package.json
|
@ -1,35 +1,42 @@
|
|||
{
|
||||
"name": "@myriation/doipjs",
|
||||
"version": "1.2.9+myriaiton.1",
|
||||
"name": "doipjs",
|
||||
"version": "0.19.0",
|
||||
"description": "Decentralized Online Identity Proofs library in Node.js",
|
||||
"type": "module",
|
||||
"main": "./src/index.js",
|
||||
"exports": {
|
||||
".": {
|
||||
"default": "./src/index.js"
|
||||
"node": "./src/index.js",
|
||||
"default": "./dist/doip.core.js"
|
||||
},
|
||||
"./fetchers": {
|
||||
"default": "./src/fetcher/index.js"
|
||||
"node": "./src/fetcher/index.js",
|
||||
"default": "./dist/doip.fetchers.js"
|
||||
},
|
||||
"./fetchers-minimal": {
|
||||
"default": "./src/fetcher/index.minimal.js"
|
||||
"node": "./src/fetcher/index.minimal.js",
|
||||
"default": "./dist/doip.fetchers.minimal.js"
|
||||
}
|
||||
},
|
||||
"packageManager": "yarn@4.3.0",
|
||||
"packageManager": "yarn@1.22.19",
|
||||
"dependencies": {
|
||||
"@openpgp/hkp-client": "^0.0.3",
|
||||
"@openpgp/wkd-client": "^0.0.4",
|
||||
"@xmpp/client": "^0.13.1",
|
||||
"@xmpp/debug": "^0.13.0",
|
||||
"axios": "^1.6.5",
|
||||
"axios": "^0.25.0",
|
||||
"browser-or-node": "^1.3.0",
|
||||
"cors": "^2.8.5",
|
||||
"entities": "^4.4.0",
|
||||
"express": "^4.17.1",
|
||||
"express-validator": "^6.10.0",
|
||||
"hash-wasm": "^4.9.0",
|
||||
"irc-upd": "^0.11.0",
|
||||
"jose": "^4.14.4",
|
||||
"merge-options": "^3.0.3",
|
||||
"openpgp": "^5.5.0",
|
||||
"rfc4648": "^1.5.2",
|
||||
"rome": "^11.0.0",
|
||||
"valid-url": "^1.0.9",
|
||||
"validator": "^13.9.0"
|
||||
},
|
||||
|
@ -39,26 +46,26 @@
|
|||
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||
"chai": "^4.2.0",
|
||||
"chai-as-promised": "^7.1.1",
|
||||
"docdash": "^2.0.2",
|
||||
"chai-match-pattern": "^1.2.0",
|
||||
"clean-jsdoc-theme": "^3.2.4",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-standard": "^17.0.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-jsdoc": "^48.0.4",
|
||||
"eslint-plugin-n": "^15.7.0",
|
||||
"eslint-plugin-promise": "^6.1.1",
|
||||
"husky": "^7.0.0",
|
||||
"jsdoc": "^4.0.2",
|
||||
"jsdoc-tsimport-plugin": "^1.0.5",
|
||||
"jsdoc": "^3.6.6",
|
||||
"license-check-and-add": "^4.0.3",
|
||||
"lint-staged": "^11.0.0",
|
||||
"minify": "^9.1",
|
||||
"mocha": "^9.2.0",
|
||||
"nodemon": "^2.0.19",
|
||||
"rollup": "^3.26.2",
|
||||
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2"
|
||||
},
|
||||
"scripts": {
|
||||
"release": "node ./prerelease.js && yarn run test && yarn run build",
|
||||
"release": "yarn run test && yarn run build",
|
||||
"build": "rm -rf ./dist/ && yarn run build:bundle && yarn run build:minify",
|
||||
"build:bundle": "rollup -c",
|
||||
"build:minify": "minify ./dist/doip.core.js > ./dist/doip.core.min.js && minify ./dist/doip.fetchers.js > ./dist/doip.fetchers.min.js && minify ./dist/doip.fetchers.minimal.js > ./dist/doip.fetchers.minimal.min.js",
|
||||
|
@ -68,12 +75,12 @@
|
|||
"docs:lib": "jsdoc -c jsdoc-lib.json -r -d ./docs -P package.json",
|
||||
"lint": "eslint ./src",
|
||||
"lint:fix": "eslint ./src --fix",
|
||||
"test": "yarn lint && yarn run license:check && yarn run mocha",
|
||||
"test": "yarn lint && yarn rome check ./src/* && yarn run license:check && yarn run mocha",
|
||||
"prepare": "husky install"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.myriation.org/myriation/doipjs"
|
||||
"url": "https://codeberg.org/keyoxide/doipjs"
|
||||
},
|
||||
"homepage": "https://js.doip.rocks",
|
||||
"keywords": [
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import * as C from './src/constants.js'
|
||||
import { readFile } from 'fs/promises'
|
||||
|
||||
const main = async () => {
|
||||
const pkg = JSON.parse(
|
||||
await readFile(
|
||||
new URL('./package.json', import.meta.url)
|
||||
)
|
||||
)
|
||||
|
||||
// Assert that the constant version equals the package version
|
||||
if (C.version !== pkg.version) {
|
||||
console.log(`!!! Mismatch between constants.js version (${C.version}) and package.json version (${pkg.version})`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
8
rome.json
Normal file
8
rome.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
"recommended": true
|
||||
}
|
||||
}
|
||||
}
|
62
src/asp.js
62
src/asp.js
|
@ -19,7 +19,6 @@ import { base32, base64url } from 'rfc4648'
|
|||
import { Claim } from './claim.js'
|
||||
import { Persona } from './persona.js'
|
||||
import { Profile } from './profile.js'
|
||||
import { ProfileType, PublicKeyEncoding, PublicKeyFetchMethod, PublicKeyType } from './enums.js'
|
||||
|
||||
const SupportedCryptoAlg = ['EdDSA', 'ES256', 'ES256K', 'ES384', 'ES512']
|
||||
|
||||
|
@ -32,9 +31,9 @@ const SupportedCryptoAlg = ['EdDSA', 'ES256', 'ES256K', 'ES384', 'ES512']
|
|||
* Fetch a public key using Web Key Directory
|
||||
* @function
|
||||
* @param {string} uri - ASPE URI
|
||||
* @returns {Promise<Profile>} The fetched profile
|
||||
* @returns {Promise<Profile>}
|
||||
* @example
|
||||
* const key = await doip.aspe.fetchASPE('aspe:domain.example:1234567890');
|
||||
* const key = doip.aspe.fetchASPE('aspe:domain.tld:1234567890');
|
||||
*/
|
||||
export async function fetchASPE (uri) {
|
||||
const re = /aspe:(.*):(.*)/
|
||||
|
@ -67,22 +66,17 @@ export async function fetchASPE (uri) {
|
|||
throw new Error(`Error fetching Keybase key: ${e.message}`)
|
||||
}
|
||||
|
||||
const profile = await parseProfileJws(profileJws, uri)
|
||||
profile.publicKey.fetch.method = PublicKeyFetchMethod.ASPE
|
||||
profile.publicKey.fetch.query = uri
|
||||
profile.publicKey.fetch.resolvedUrl = profileUrl
|
||||
|
||||
return profile
|
||||
return await parseProfileJws(profileJws, uri)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JWS and extract the profile it contains
|
||||
* Fetch a public key using Web Key Directory
|
||||
* @function
|
||||
* @param {string} profileJws - Compact-Serialized profile JWS
|
||||
* @param {string} uri - The ASPE URI associated with the profile
|
||||
* @returns {Promise<Profile>} The extracted profile
|
||||
* @param {string} uri - The ASPE URI associated with the profile
|
||||
* @returns {Promise<Profile>}
|
||||
* @example
|
||||
* const key = await doip.aspe.parseProfileJws('...', 'aspe:domain.example:123');
|
||||
* const key = doip.aspe.parseProfileJws('...');
|
||||
*/
|
||||
export async function parseProfileJws (profileJws, uri) {
|
||||
const matches = uri.match(/aspe:(.*):(.*)/)
|
||||
|
@ -130,49 +124,23 @@ export async function parseProfileJws (profileJws, uri) {
|
|||
const profileName = payloadJson['http://ariadne.id/name']
|
||||
/** @type {string} */
|
||||
const profileDescription = payloadJson['http://ariadne.id/description']
|
||||
/** @type {string} */
|
||||
const profileThemeColor = payloadJson['http://ariadne.id/color']
|
||||
/** @type {Array<string>} */
|
||||
/** @type {string[]} */
|
||||
const profileClaims = payloadJson['http://ariadne.id/claims']
|
||||
|
||||
const profileClaimsParsed = profileClaims.map(x => new Claim(x, uri))
|
||||
|
||||
const pe = new Persona(profileName, profileClaimsParsed)
|
||||
if (profileDescription) {
|
||||
pe.setDescription(profileDescription)
|
||||
}
|
||||
if (profileThemeColor && /^#([0-9A-F]{3}){1,2}$/i.test(profileThemeColor)) {
|
||||
pe.themeColor = profileThemeColor
|
||||
}
|
||||
const pe = new Persona(profileName, profileDescription || '', profileClaimsParsed)
|
||||
const pr = new Profile([pe])
|
||||
pr.primaryPersona = 0
|
||||
|
||||
const profile = new Profile(ProfileType.ASP, uri, [pe])
|
||||
profile.publicKey.fingerprint = fp
|
||||
profile.publicKey.encoding = PublicKeyEncoding.JWK
|
||||
profile.publicKey.encodedKey = JSON.stringify(protectedHeader.jwk)
|
||||
profile.publicKey.key = protectedHeader.jwk
|
||||
|
||||
switch (protectedHeader.alg) {
|
||||
case 'ES256':
|
||||
profile.publicKey.keyType = PublicKeyType.ES256
|
||||
break
|
||||
|
||||
case 'EdDSA':
|
||||
profile.publicKey.keyType = PublicKeyType.EDDSA
|
||||
break
|
||||
|
||||
default:
|
||||
profile.publicKey.keyType = PublicKeyType.UNKNOWN
|
||||
break
|
||||
}
|
||||
|
||||
return profile
|
||||
return pr
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the fingerprint for {@link https://github.com/panva/jose/blob/main/docs/interfaces/types.JWK.md JWK} keys
|
||||
* Compute the fingerprint for JWK keys
|
||||
* @function
|
||||
* @param {import('jose').JWK} key - The JWK public key for which to compute the fingerprint
|
||||
* @returns {Promise<string>} The computed fingerprint
|
||||
* @param {import('jose').JWK} key
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
export async function computeJwkFingerprint (key) {
|
||||
const thumbprint = await calculateJwkThumbprint(key, 'sha512')
|
||||
|
|
251
src/claim.js
251
src/claim.js
|
@ -18,30 +18,50 @@ import { isUri } from 'valid-url'
|
|||
import mergeOptions from 'merge-options'
|
||||
import { fetch } from './proofs.js'
|
||||
import { run } from './verifications.js'
|
||||
import { list, data as _data } from './serviceProviders/index.js'
|
||||
import { list, data as _data } from './claimDefinitions/index.js'
|
||||
import { opts as _opts } from './defaults.js'
|
||||
import { ClaimStatus } from './enums.js'
|
||||
import { ServiceProvider } from './serviceProvider.js'
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @classdesc Identity claim
|
||||
* @classdesc OpenPGP-based identity claim
|
||||
* @property {string} uri - The claim's URI
|
||||
* @property {string} fingerprint - The fingerprint to verify the claim against
|
||||
* @property {number} status - The current status code of the claim
|
||||
* @property {string} status - The current status of the claim
|
||||
* @property {Array<object>} matches - The claim definitions matched against the URI
|
||||
* @example
|
||||
* const claim = doip.Claim();
|
||||
* const claim = doip.Claim('dns:domain.tld?type=TXT');
|
||||
* const claim = doip.Claim('dns:domain.tld?type=TXT', '123abc123abc');
|
||||
* @property {object} verification - The result of the verification process
|
||||
*/
|
||||
export class Claim {
|
||||
/**
|
||||
* Initialize a Claim object
|
||||
* @param {string} [uri] - The URI of the identity claim
|
||||
* @constructor
|
||||
* @param {string|object} [uri] - The URI of the identity claim or a JSONified Claim instance
|
||||
* @param {string} [fingerprint] - The fingerprint of the OpenPGP key
|
||||
* @example
|
||||
* const claim = doip.Claim();
|
||||
* const claim = doip.Claim('dns:domain.tld?type=TXT');
|
||||
* const claim = doip.Claim('dns:domain.tld?type=TXT', '123abc123abc');
|
||||
* const claimAlt = doip.Claim(JSON.stringify(claim));
|
||||
*/
|
||||
constructor (uri, fingerprint) {
|
||||
// Import JSON
|
||||
if (typeof uri === 'object' && 'claimVersion' in uri) {
|
||||
const data = uri
|
||||
switch (data.claimVersion) {
|
||||
case 1:
|
||||
this._uri = data.uri
|
||||
this._fingerprint = data.fingerprint
|
||||
this._status = data.status
|
||||
this._matches = data.matches
|
||||
this._verification = data.verification
|
||||
break
|
||||
|
||||
default:
|
||||
throw new Error('Invalid claim version')
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Verify validity of URI
|
||||
if (uri && !isUri(uri)) {
|
||||
throw new Error('Invalid URI')
|
||||
|
@ -57,61 +77,11 @@ export class Claim {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this._uri = uri || ''
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
this._fingerprint = fingerprint || ''
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this._status = ClaimStatus.INIT
|
||||
/**
|
||||
* @type {Array<ServiceProvider>}
|
||||
*/
|
||||
this._matches = []
|
||||
}
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {*} claimObject - JSON representation of a claim
|
||||
* @returns {Claim} Parsed claim
|
||||
* @throws Will throw an error if the JSON object can't be coerced into a Claim
|
||||
* @example
|
||||
* doip.Claim.fromJSON(JSON.stringify(claim));
|
||||
*/
|
||||
static fromJSON (claimObject) {
|
||||
/** @type {Claim} */
|
||||
let claim
|
||||
let result
|
||||
|
||||
if (typeof claimObject === 'object' && 'claimVersion' in claimObject) {
|
||||
switch (claimObject.claimVersion) {
|
||||
case 1:
|
||||
result = importJsonClaimVersion1(claimObject)
|
||||
if (result instanceof Error) {
|
||||
throw result
|
||||
}
|
||||
claim = result
|
||||
break
|
||||
|
||||
case 2:
|
||||
result = importJsonClaimVersion2(claimObject)
|
||||
if (result instanceof Error) {
|
||||
throw result
|
||||
}
|
||||
claim = result
|
||||
break
|
||||
|
||||
default:
|
||||
throw new Error('Invalid claim version')
|
||||
}
|
||||
}
|
||||
|
||||
return claim
|
||||
this._verification = {}
|
||||
}
|
||||
|
||||
get uri () {
|
||||
|
@ -133,6 +103,13 @@ export class Claim {
|
|||
return this._matches
|
||||
}
|
||||
|
||||
get verification () {
|
||||
if (this._status !== ClaimStatus.VERIFIED) {
|
||||
throw new Error('This claim has not yet been verified')
|
||||
}
|
||||
return this._verification
|
||||
}
|
||||
|
||||
set uri (uri) {
|
||||
if (this._status !== ClaimStatus.INIT) {
|
||||
throw new Error(
|
||||
|
@ -166,6 +143,10 @@ export class Claim {
|
|||
throw new Error("Cannot change a claim's matches")
|
||||
}
|
||||
|
||||
set verification (anything) {
|
||||
throw new Error("Cannot change a claim's verification result")
|
||||
}
|
||||
|
||||
/**
|
||||
* Match the claim's URI to candidate definitions
|
||||
* @function
|
||||
|
@ -194,7 +175,7 @@ export class Claim {
|
|||
return true
|
||||
}
|
||||
|
||||
if (candidate.claim.uriIsAmbiguous) {
|
||||
if (candidate.match.isAmbiguous) {
|
||||
// Add to the possible candidates
|
||||
this._matches.push(candidate)
|
||||
} else {
|
||||
|
@ -207,7 +188,7 @@ export class Claim {
|
|||
return true
|
||||
})
|
||||
|
||||
this._status = this._matches.length === 0 ? ClaimStatus.NO_MATCHES : ClaimStatus.MATCHED
|
||||
this._status = ClaimStatus.MATCHED
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -215,14 +196,15 @@ export class Claim {
|
|||
* checked for the fingerprint. The verification stops when either a positive
|
||||
* result was obtained, or an unambiguous claim definition was processed
|
||||
* regardless of the result.
|
||||
* @async
|
||||
* @function
|
||||
* @param {import('./types').VerificationConfig} [opts] - Options for proxy, fetchers
|
||||
* @param {object} [opts] - Options for proxy, fetchers
|
||||
*/
|
||||
async verify (opts) {
|
||||
if (this._status === ClaimStatus.INIT) {
|
||||
throw new Error('This claim has not yet been matched')
|
||||
}
|
||||
if (this._status >= 200) {
|
||||
if (this._status === ClaimStatus.VERIFIED) {
|
||||
throw new Error('This claim has already been verified')
|
||||
}
|
||||
if (this._fingerprint.length === 0) {
|
||||
|
@ -234,17 +216,18 @@ export class Claim {
|
|||
|
||||
// If there are no matches
|
||||
if (this._matches.length === 0) {
|
||||
this.status = ClaimStatus.NO_MATCHES
|
||||
this._verification = {
|
||||
result: false,
|
||||
completed: true,
|
||||
proof: {},
|
||||
errors: ['No matches for claim']
|
||||
}
|
||||
}
|
||||
|
||||
// For each match
|
||||
for (let index = 0; index < this._matches.length; index++) {
|
||||
// Continue if a result was already obtained
|
||||
if (this._status >= 200) { continue }
|
||||
|
||||
let claimData = this._matches[index]
|
||||
|
||||
/** @type {import('./types').VerificationResult | null} */
|
||||
let verificationResult = null
|
||||
let proofData = null
|
||||
let proofFetchError
|
||||
|
@ -267,18 +250,11 @@ export class Claim {
|
|||
viaProxy: proofData.viaProxy
|
||||
}
|
||||
|
||||
// Validate the result
|
||||
const def = _data[claimData.about.id]
|
||||
if (def.functions?.validate && verificationResult.completed && verificationResult.result) {
|
||||
try {
|
||||
(verificationResult.result = await def.functions.validate(claimData, proofData, verificationResult, opts))
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
// Post process the data
|
||||
const def = _data[claimData.serviceprovider.name]
|
||||
if (def.functions?.postprocess) {
|
||||
try {
|
||||
({ claimData, proofData } = await def.functions.postprocess(claimData, proofData, opts))
|
||||
({ claimData, proofData } = def.functions.postprocess(claimData, proofData))
|
||||
} catch (_) {}
|
||||
}
|
||||
} else {
|
||||
|
@ -286,7 +262,7 @@ export class Claim {
|
|||
verificationResult = verificationResult || {
|
||||
result: false,
|
||||
completed: true,
|
||||
proof: null,
|
||||
proof: {},
|
||||
errors: [proofFetchError]
|
||||
}
|
||||
}
|
||||
|
@ -296,13 +272,25 @@ export class Claim {
|
|||
continue
|
||||
}
|
||||
|
||||
if (verificationResult.result) {
|
||||
this._status = verificationResult.proof.viaProxy ? ClaimStatus.VERIFIED_VIA_PROXY : ClaimStatus.VERIFIED
|
||||
if (verificationResult.completed) {
|
||||
// Store the result, keep a single match and stop verifying
|
||||
this._verification = verificationResult
|
||||
this._matches = [claimData]
|
||||
index = this._matches.length
|
||||
}
|
||||
}
|
||||
|
||||
this._status = this._status >= 200 ? this._status : ClaimStatus.NO_PROOF_FOUND
|
||||
// Fail safe verification result
|
||||
this._verification = Object.keys(this._verification).length > 0
|
||||
? this._verification
|
||||
: {
|
||||
result: false,
|
||||
completed: true,
|
||||
proof: {},
|
||||
errors: []
|
||||
}
|
||||
|
||||
this._status = ClaimStatus.VERIFIED
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -310,115 +298,32 @@ export class Claim {
|
|||
* of the candidates is unambiguous. An ambiguous claim should never be
|
||||
* displayed in an user interface when its result is negative.
|
||||
* @function
|
||||
* @returns {boolean} Whether the claim is ambiguous
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isAmbiguous () {
|
||||
if (this._status < ClaimStatus.MATCHED) {
|
||||
if (this._status === ClaimStatus.INIT) {
|
||||
throw new Error('The claim has not been matched yet')
|
||||
}
|
||||
if (this._matches.length === 0) {
|
||||
throw new Error('The claim has no matches')
|
||||
}
|
||||
if (this._status >= 200 && this._status < 300) return false
|
||||
return this._matches.length > 1 || this._matches[0].claim.uriIsAmbiguous
|
||||
return this._matches.length > 1 || this._matches[0].match.isAmbiguous
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the Claim object. Useful when transferring
|
||||
* data between instances/machines.
|
||||
* @function
|
||||
* @returns {object} JSON reprentation of the claim
|
||||
* @returns {object}
|
||||
*/
|
||||
toJSON () {
|
||||
let displayProfileName = this._uri
|
||||
let displayProfileUrl = null
|
||||
let displayProofUrl = null
|
||||
let displayServiceProviderName = null
|
||||
let displayServiceProviderId = null
|
||||
|
||||
if (this._status >= ClaimStatus.MATCHED && this._matches.length > 0 && !this.isAmbiguous()) {
|
||||
displayProfileName = this._matches[0].profile.display
|
||||
displayProfileUrl = this._matches[0].profile.uri
|
||||
displayProofUrl = this._matches[0].proof.request.uri
|
||||
displayServiceProviderName = this._matches[0].about.name
|
||||
displayServiceProviderId = this._matches[0].about.id
|
||||
}
|
||||
|
||||
return {
|
||||
claimVersion: 2,
|
||||
claimVersion: 1,
|
||||
uri: this._uri,
|
||||
proofs: [this._fingerprint],
|
||||
matches: this._matches.map(x => x.toJSON()),
|
||||
fingerprint: this._fingerprint,
|
||||
status: this._status,
|
||||
display: {
|
||||
profileName: displayProfileName,
|
||||
profileUrl: displayProfileUrl,
|
||||
proofUrl: displayProofUrl,
|
||||
serviceProviderName: displayServiceProviderName,
|
||||
serviceProviderId: displayServiceProviderId
|
||||
}
|
||||
matches: this._matches,
|
||||
verification: this._verification
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param {object} claimObject - JSON representation of a claim
|
||||
* @returns {Claim | Error} Parsed claim
|
||||
*/
|
||||
function importJsonClaimVersion1 (claimObject) {
|
||||
if (!('claimVersion' in claimObject && claimObject.claimVersion === 1)) {
|
||||
return new Error('Invalid claim')
|
||||
}
|
||||
|
||||
const claim = new Claim()
|
||||
|
||||
claim._uri = claimObject.uri
|
||||
claim._fingerprint = claimObject.fingerprint
|
||||
claim._matches = claimObject.matches.map(x => new ServiceProvider(x))
|
||||
|
||||
if (claimObject.status === 'init') {
|
||||
claim._status = 100
|
||||
}
|
||||
if (claimObject.status === 'matched') {
|
||||
if (claimObject.matches.length === 0) {
|
||||
claim._status = 301
|
||||
}
|
||||
claim._status = 101
|
||||
}
|
||||
|
||||
if (!('result' in claimObject.verification && 'errors' in claimObject.verification)) {
|
||||
claim._status = 400
|
||||
}
|
||||
if (claimObject.verification.errors.length > 0) {
|
||||
claim._status = 400
|
||||
}
|
||||
if (claimObject.verification.result && claimObject.verification.proof.viaProxy) {
|
||||
claim._status = 201
|
||||
}
|
||||
if (claimObject.verification.result && !claimObject.verification.proof.viaProxy) {
|
||||
claim._status = 200
|
||||
}
|
||||
|
||||
return claim
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param {object} claimObject - JSON representation of a claim
|
||||
* @returns {Claim | Error} Parsed claim
|
||||
*/
|
||||
function importJsonClaimVersion2 (claimObject) {
|
||||
if (!('claimVersion' in claimObject && claimObject.claimVersion === 2)) {
|
||||
return new Error('Invalid claim')
|
||||
}
|
||||
|
||||
const claim = new Claim()
|
||||
|
||||
claim._uri = claimObject.uri
|
||||
claim._fingerprint = claimObject.proofs[0]
|
||||
claim._matches = claimObject.matches.map(x => new ServiceProvider(x))
|
||||
claim._status = claimObject.status
|
||||
|
||||
return claim
|
||||
}
|
||||
|
|
113
src/claimDefinitions/activitypub.js
Normal file
113
src/claimDefinitions/activitypub.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Copyright 2022 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import * as E from '../enums.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'activitypub'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: true
|
||||
},
|
||||
profile: {
|
||||
display: uri,
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
fetcher: E.Fetcher.ACTIVITYPUB,
|
||||
access: E.ProofAccess.GENERIC,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: uri
|
||||
}
|
||||
}
|
||||
},
|
||||
claim: [
|
||||
{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['summary']
|
||||
},
|
||||
{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['attachment', 'value']
|
||||
},
|
||||
{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['content']
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export const functions = {
|
||||
postprocess: (claimData, proofData) => {
|
||||
claimData.profile.display = `@${proofData.result.preferredUsername}@${new URL(proofData.result.url).hostname}`
|
||||
return { claimData, proofData }
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://domain.org',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/@/alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/@alice',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/@alice/123456',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/u/alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/users/alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/users/alice/123456',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'http://domain.org/alice',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Discourse service provider ({@link https://docs.keyoxide.org/service-providers/discourse/|Keyoxide docs})
|
||||
* @module serviceProviders/discourse
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.activitypub.processURI('https://domain.example/u/alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)\/u\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'discourse',
|
||||
name: 'Discourse',
|
||||
homepage: 'https://www.discourse.org'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'discourse'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: true
|
||||
},
|
||||
profile: {
|
||||
display: `${match[2]}@${match[1]}`,
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString().toString(),
|
||||
uriIsAmbiguous: true
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://${match[1]}/u/${match[2]}.json`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['user', 'bio_raw']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['user', 'bio_raw']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,61 +13,49 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* DNS service provider ({@link https://docs.keyoxide.org/service-providers/dns/|Keyoxide docs})
|
||||
* @module serviceProviders/dns
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.dns.processURI('dns:domain.example?type=TXT');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^dns:([a-zA-Z0-9.\-_]*)(?:\?(.*))?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'dns',
|
||||
name: 'DNS'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'dns'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri: `https://${match[1]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: null,
|
||||
request: {
|
||||
uri: null,
|
||||
fetcher: E.Fetcher.DNS,
|
||||
accessRestriction: E.ProofAccessRestriction.SERVER,
|
||||
access: E.ProofAccess.SERVER,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
domain: match[1]
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['records', 'txt']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['records', 'txt']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Forem service provider ({@link https://docs.keyoxide.org/service-providers/forem/|Keyoxide docs})
|
||||
* @module serviceProviders/forem
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.forem.processURI('https://domain.example/alice/title');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'forem',
|
||||
name: 'Forem',
|
||||
homepage: 'https://www.forem.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'forem'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: true
|
||||
},
|
||||
profile: {
|
||||
display: `${match[2]}@${match[1]}`,
|
||||
uri: `https://${match[1]}/${match[2]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString().toString(),
|
||||
uriIsAmbiguous: true
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://${match[1]}/api/articles/${match[2]}/${match[3]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['body_markdown']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['body_markdown']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
79
src/claimDefinitions/forgejo.js
Normal file
79
src/claimDefinitions/forgejo.js
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
Copyright 2023 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import * as E from '../enums.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'forgejo'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: true
|
||||
},
|
||||
profile: {
|
||||
display: `${match[2]}@${match[1]}`,
|
||||
uri: `https://${match[1]}/${match[2]}`,
|
||||
qr: null
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://${match[1]}/api/v1/repos/${match[2]}/${match[3]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['description']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://domain.org/alice/forgejo_proof',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/alice/forgejo_proof/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/alice/other_proof',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/alice',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Gitea service provider ({@link https://docs.keyoxide.org/service-providers/gitea/|Keyoxide docs})
|
||||
* @module serviceProviders/gitea
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.gitea.processURI('https://domain.example/alice/repo');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'gitea',
|
||||
name: 'Gitea',
|
||||
homepage: 'https://about.gitea.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'gitea'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: true
|
||||
},
|
||||
profile: {
|
||||
display: `${match[2]}@${match[1]}`,
|
||||
uri: `https://${match[1]}/${match[2]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: true
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://${match[1]}/api/v1/repos/${match[2]}/${match[3]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['description']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['description']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,71 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Github service provider ({@link https://docs.keyoxide.org/service-providers/github/|Keyoxide docs})
|
||||
* @module serviceProviders/github
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.github.processURI('https://gist.github.com/alice/title');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/gist\.github\.com\/(.*)\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'github',
|
||||
name: 'GitHub',
|
||||
homepage: 'https://github.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'github'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri: `https://github.com/${match[1]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
access: E.ProofAccess.GENERIC,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://api.github.com/gists/${match[2]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['files', 'proof.md', 'content']
|
||||
},
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['files', 'openpgp.md', 'content']
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['files', 'openpgp.md', 'content']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,62 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Gitlab service provider ({@link https://docs.keyoxide.org/service-providers/gitlab/|Keyoxide docs})
|
||||
* @module serviceProviders/gitlab
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.gitlab.processURI('https://domain.example/alice/repo');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)\/(.*)\/gitlab_proof\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'gitlab',
|
||||
name: 'GitLab',
|
||||
homepage: 'https://about.gitlab.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'gitlab'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: true
|
||||
},
|
||||
profile: {
|
||||
display: `${match[2]}@${match[1]}`,
|
||||
uri: `https://${match[1]}/${match[2]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: true
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
access: E.ProofAccess.GENERIC,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://${match[1]}/api/v4/projects/${match[2]}%2Fgitlab_proof`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['description']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['description']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Hackernews service provider ({@link https://docs.keyoxide.org/service-providers/hackernews/|Keyoxide docs})
|
||||
* @module serviceProviders/hackernews
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.hackernews.processURI('https://news.ycombinator.com/user?id=alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/news\.ycombinator\.com\/user\?id=(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'hackernews',
|
||||
name: 'Hacker News',
|
||||
homepage: 'https://news.ycombinator.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'hackernews'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: `https://hacker-news.firebaseio.com/v0/user/${match[1]}.json`,
|
||||
request: {
|
||||
uri: `https://hacker-news.firebaseio.com/v0/user/${match[1]}.json`,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://hacker-news.firebaseio.com/v0/user/${match[1]}.json`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.HTML,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['about']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.HTML,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['about']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,8 +13,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import * as aspe from './aspe.js'
|
||||
import * as openpgp from './openpgp.js'
|
||||
import * as dns from './dns.js'
|
||||
import * as irc from './irc.js'
|
||||
import * as xmpp from './xmpp.js'
|
||||
|
@ -27,7 +25,7 @@ import * as lichess from './lichess.js'
|
|||
import * as hackernews from './hackernews.js'
|
||||
import * as lobsters from './lobsters.js'
|
||||
import * as forem from './forem.js'
|
||||
import * as forgejo from './forgejo.js'
|
||||
// import * as forgejo from './forgejo.js'
|
||||
import * as gitea from './gitea.js'
|
||||
import * as gitlab from './gitlab.js'
|
||||
import * as github from './github.js'
|
||||
|
@ -37,13 +35,8 @@ import * as owncast from './owncast.js'
|
|||
import * as stackexchange from './stackexchange.js'
|
||||
import * as keybase from './keybase.js'
|
||||
import * as opencollective from './opencollective.js'
|
||||
import * as orcid from './orcid.js'
|
||||
import * as pronounscc from './pronounscc.js'
|
||||
import * as discord from './discord.js'
|
||||
|
||||
const _data = {
|
||||
aspe,
|
||||
openpgp,
|
||||
dns,
|
||||
irc,
|
||||
xmpp,
|
||||
|
@ -56,7 +49,7 @@ const _data = {
|
|||
hackernews,
|
||||
lobsters,
|
||||
forem,
|
||||
forgejo,
|
||||
// forgejo,
|
||||
gitea,
|
||||
gitlab,
|
||||
github,
|
||||
|
@ -65,10 +58,7 @@ const _data = {
|
|||
owncast,
|
||||
stackexchange,
|
||||
keybase,
|
||||
opencollective,
|
||||
orcid,
|
||||
pronounscc,
|
||||
discord
|
||||
opencollective
|
||||
}
|
||||
|
||||
export const list = Object.keys(_data)
|
|
@ -13,62 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* IRC service provider ({@link https://docs.keyoxide.org/service-providers/irc/|Keyoxide docs})
|
||||
* @module serviceProviders/irc
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.irc.processURI('irc://domain.example/alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^irc:\/\/(.*)\/([a-zA-Z0-9\-[\]\\`_^{|}]*)/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'irc',
|
||||
name: 'IRC'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'communication',
|
||||
name: 'irc'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: `${match[1]}/${match[2]}`,
|
||||
display: `irc://${match[1]}/${match[2]}`,
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: null,
|
||||
request: {
|
||||
uri: null,
|
||||
fetcher: E.Fetcher.IRC,
|
||||
accessRestriction: E.ProofAccessRestriction.SERVER,
|
||||
access: E.ProofAccess.SERVER,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
domain: match[1],
|
||||
nick: match[2]
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: []
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: []
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Keybase service provider ({@link https://docs.keyoxide.org/service-providers/keybase/|Keyoxide docs})
|
||||
* @module serviceProviders/keybase
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.keybase.processURI('https://keybase.io/alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/keybase.io\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'keybase',
|
||||
name: 'keybase',
|
||||
homepage: 'https://keybase.io'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'keybase'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: `https://keybase.io/_/api/1.0/user/lookup.json?username=${match[1]}`,
|
||||
request: {
|
||||
uri: `https://keybase.io/_/api/1.0/user/lookup.json?username=${match[1]}`,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://keybase.io/_/api/1.0/user/lookup.json?username=${match[1]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['them', 'public_keys', 'primary', 'key_fingerprint']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['them', 'public_keys', 'primary', 'key_fingerprint']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Liberapay service provider ({@link https://docs.keyoxide.org/service-providers/liberapay/|Keyoxide docs})
|
||||
* @module serviceProviders/liberapay
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.liberapay.processURI('https://liberapay.com/alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/liberapay\.com\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'liberapay',
|
||||
name: 'Liberapay',
|
||||
homepage: 'https://liberapay.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'liberapay'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
access: E.ProofAccess.GENERIC,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://liberapay.com/${match[1]}/public.json`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['statements', 'content']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['statements', 'content']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Lichess service provider ({@link https://docs.keyoxide.org/service-providers/lichess/|Keyoxide docs})
|
||||
* @module serviceProviders/lichess
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.lichess.processURI('https://lichess.org/@/alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/lichess\.org\/@\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'lichess',
|
||||
name: 'Lichess',
|
||||
homepage: 'https://lichess.org'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'lichess'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: `https://lichess.org/api/user/${match[1]}`,
|
||||
request: {
|
||||
uri: `https://lichess.org/api/user/${match[1]}`,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
access: E.ProofAccess.GENERIC,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://lichess.org/api/user/${match[1]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['profile', 'links']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['profile', 'links']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
75
src/claimDefinitions/lobsters.js
Normal file
75
src/claimDefinitions/lobsters.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2021 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import * as E from '../enums.js'
|
||||
|
||||
export const reURI = /^https:\/\/lobste\.rs\/u\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'lobsters'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
proof: {
|
||||
uri: `https://lobste.rs/u/${match[1]}.json`,
|
||||
request: {
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://lobste.rs/u/${match[1]}.json`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['about']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://lobste.rs/u/Alice',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://lobste.rs/u/Alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/u/Alice',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -13,23 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Matrix service provider ({@link https://docs.keyoxide.org/service-providers/matrix/|Keyoxide docs})
|
||||
* @module serviceProviders/matrix
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.matrix.processURI('matrix:u/...');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^matrix:u\/(?:@)?([^@:]*:[^?]*)(\?.*)?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
@ -50,42 +40,39 @@ export function processURI (uri) {
|
|||
const profileUrl = `https://matrix.to/#/@${match[1]}`
|
||||
const eventUrl = `https://matrix.to/#/${paramRoomId}/${paramEventId}`
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'matrix',
|
||||
name: 'Matrix',
|
||||
homepage: 'https://matrix.org'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'communication',
|
||||
name: 'matrix'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: `@${match[1]}`,
|
||||
uri: profileUrl,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: eventUrl,
|
||||
request: {
|
||||
uri: eventUrl,
|
||||
fetcher: E.Fetcher.MATRIX,
|
||||
accessRestriction: E.ProofAccessRestriction.GRANTED,
|
||||
access: E.ProofAccess.GRANTED,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
eventId: paramEventId,
|
||||
roomId: paramRoomId
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['content', 'body']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['content', 'body']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* OpenCollective service provider ({@link https://docs.keyoxide.org/service-providers/opencollective/|Keyoxide docs})
|
||||
* @module serviceProviders/opencollective
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.opencollective.processURI('https://opencollective.com/alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/opencollective\.com\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'opencollective',
|
||||
name: 'Open Collective',
|
||||
homepage: 'https://opencollective.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'opencollective'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.GRAPHQL,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: 'https://api.opencollective.com/graphql/v2',
|
||||
query: `{ "query": "query { account(slug: \\"${match[1]}\\") { longDescription } }" }`
|
||||
query: `{ "query": "query { collective(slug: \\"${match[1]}\\") { longDescription } }" }`
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['data', 'account', 'longDescription']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['data', 'collective', 'longDescription']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Owncast service provider ({@link https://docs.keyoxide.org/service-providers/owncast/|Keyoxide docs})
|
||||
* @module serviceProviders/owncast
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.owncast.processURI('https://domain.example');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'owncast',
|
||||
name: 'Owncast',
|
||||
homepage: 'https://owncast.online'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'owncast'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: true
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: true
|
||||
},
|
||||
proof: {
|
||||
uri: `${uri}/api/config`,
|
||||
request: {
|
||||
uri: `${uri}/api/config`,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
access: E.ProofAccess.GENERIC,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `${uri}/api/config`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['socialHandles', 'url']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.FINGERPRINT,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['socialHandles', 'url']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Reddit service provider ({@link https://docs.keyoxide.org/service-providers/reddit/|Keyoxide docs})
|
||||
* @module serviceProviders/reddit
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.reddit.processURI('https://reddit.com/...');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(?:www\.)?reddit\.com\/user\/(.*)\/comments\/(.*)\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'reddit',
|
||||
name: 'Reddit',
|
||||
homepage: 'https://reddit.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'reddit'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri: `https://www.reddit.com/user/${match[1]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://www.reddit.com/user/${match[1]}/comments/${match[2]}.json`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['data', 'children', 'data', 'selftext']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['data', 'children', 'data', 'selftext']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,65 +13,52 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* StackExchange service provider ({@link https://docs.keyoxide.org/service-providers/stackexchange/|Keyoxide docs})
|
||||
* @module serviceProviders/stackexchange
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.stackexchange.processURI('https://stackoverflow.com/users/123/alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*(?:askubuntu|mathoverflow|serverfault|stackapps|stackoverflow|superuser)|.+\.stackexchange)\.com\/users\/(\d+)/
|
||||
const reStackExchange = /\.stackexchange$/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const [, domain, id] = uri.match(reURI)
|
||||
const site = domain.replace(reStackExchange, '')
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'stackexchange',
|
||||
name: 'Stack Exchange',
|
||||
homepage: 'https://stackexchange.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'stackexchange'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: `${id}@${site}`,
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: `https://${domain}.com/users/${id}?tab=profile`,
|
||||
request: {
|
||||
uri: `https://${domain}.com/users/${id}?tab=profile`,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
access: E.ProofAccess.GENERIC,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
url: `https://api.stackexchange.com/2.3/users/${id}?site=${site}&filter=!AH)b5JqVyImf`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['items', 'about_me']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['items', 'about_me']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,63 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Telegram service provider ({@link https://docs.keyoxide.org/service-providers/telegram/|Keyoxide docs})
|
||||
* @module serviceProviders/telegram
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.telegram.processURI('https://t.me/alice?proof=mygroup');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /https:\/\/t.me\/([A-Za-z0-9_]{5,32})\?proof=([A-Za-z0-9_]{5,32})/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'telegram',
|
||||
name: 'Telegram',
|
||||
homepage: 'https://telegram.org'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'communication',
|
||||
name: 'telegram'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: `@${match[1]}`,
|
||||
uri: `https://t.me/${match[1]}`,
|
||||
qr: `https://t.me/${match[1]}`
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: `https://t.me/${match[2]}`,
|
||||
request: {
|
||||
uri: `https://t.me/${match[2]}`,
|
||||
fetcher: E.Fetcher.TELEGRAM,
|
||||
accessRestriction: E.ProofAccessRestriction.GRANTED,
|
||||
access: E.ProofAccess.GRANTED,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
user: match[1],
|
||||
chat: match[2]
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['text']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['text']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,23 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Twitter service provider ({@link https://docs.keyoxide.org/service-providers/twitter/|Keyoxide docs})
|
||||
* @module serviceProviders/twitter
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.twitter.processURI('https://twitter.com/alice/status/123456789');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/twitter\.com\/(.*)\/status\/([0-9]*)(?:\?.*)?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
@ -38,43 +28,40 @@ export function processURI (uri) {
|
|||
urlsp.set('url', match[0])
|
||||
urlsp.set('omit_script', '1')
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'twitter',
|
||||
name: 'Twitter',
|
||||
homepage: 'https://twitter.com'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'web',
|
||||
name: 'twitter'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: `@${match[1]}`,
|
||||
uri: `https://twitter.com/${match[1]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri,
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
access: E.ProofAccess.NOCORS,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
// Returns an oembed json object with the tweet content in html form
|
||||
url: `https://publish.twitter.com/oembed?${urlsp}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['html']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['html']
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -13,62 +13,49 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* XMPP service provider ({@link https://docs.keyoxide.org/service-providers/xmpp/|Keyoxide docs})
|
||||
* @module serviceProviders/xmpp
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.xmpp.processURI('xmpp:alice@domain.example');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^xmpp:([a-zA-Z0-9.\-_]*)@([a-zA-Z0-9.\-_]*)(?:\?(.*))?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
* @param {string} uri
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'xmpp',
|
||||
name: 'XMPP',
|
||||
homepage: 'https://xmpp.org'
|
||||
return {
|
||||
serviceprovider: {
|
||||
type: 'communication',
|
||||
name: 'xmpp'
|
||||
},
|
||||
match: {
|
||||
regularExpression: reURI,
|
||||
isAmbiguous: false
|
||||
},
|
||||
profile: {
|
||||
display: `${match[1]}@${match[2]}`,
|
||||
uri,
|
||||
qr: uri
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
uri: null,
|
||||
request: {
|
||||
uri: null,
|
||||
fetcher: E.Fetcher.XMPP,
|
||||
accessRestriction: E.ProofAccessRestriction.SERVER,
|
||||
access: E.ProofAccess.SERVER,
|
||||
format: E.ProofFormat.JSON,
|
||||
data: {
|
||||
id: `${match[1]}@${match[2]}`
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: []
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
claim: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: []
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
|
@ -22,4 +22,4 @@ limitations under the License.
|
|||
* doip.js library version
|
||||
* @constant {string}
|
||||
*/
|
||||
export const version = '1.2.9+myriaiton.1'
|
||||
export const version = '0.20.0'
|
||||
|
|
|
@ -21,8 +21,26 @@ import { ProxyPolicy } from './enums.js'
|
|||
*/
|
||||
|
||||
/**
|
||||
* The default claim verification config used throughout the library
|
||||
* @type {import('./types').VerificationConfig}
|
||||
* The default options used throughout the library
|
||||
* @constant {object}
|
||||
* @property {object} proxy - Options related to the proxy
|
||||
* @property {string|null} proxy.hostname - The hostname of the proxy
|
||||
* @property {string} proxy.policy - The policy that defines when to use a proxy ({@link module:enums~ProxyPolicy|here})
|
||||
* @property {object} claims - Options related to claim verification
|
||||
* @property {object} claims.activitypub - Options related to the verification of activitypub claims
|
||||
* @property {string|null} claims.activitypub.url - The URL of the verifier account
|
||||
* @property {string|null} claims.activitypub.privateKey - The private key to sign the request
|
||||
* @property {object} claims.irc - Options related to the verification of IRC claims
|
||||
* @property {string|null} claims.irc.nick - The nick that the library uses to connect to the IRC server
|
||||
* @property {object} claims.matrix - Options related to the verification of Matrix claims
|
||||
* @property {string|null} claims.matrix.instance - The server hostname on which the library can log in
|
||||
* @property {string|null} claims.matrix.accessToken - The access token required to identify the library ({@link https://www.matrix.org/docs/guides/client-server-api|Matrix docs})
|
||||
* @property {object} claims.telegram - Options related to the verification of Telegram claims
|
||||
* @property {string|null} claims.telegram.token - The Telegram API's token ({@link https://core.telegram.org/bots/api#authorizing-your-bot|Telegram docs})
|
||||
* @property {object} claims.xmpp - Options related to the verification of XMPP claims
|
||||
* @property {string|null} claims.xmpp.service - The server hostname on which the library can log in
|
||||
* @property {string|null} claims.xmpp.username - The username used to log in
|
||||
* @property {string|null} claims.xmpp.password - The password used to log in
|
||||
*/
|
||||
export const opts = {
|
||||
proxy: {
|
||||
|
@ -35,8 +53,7 @@ export const opts = {
|
|||
privateKey: null
|
||||
},
|
||||
irc: {
|
||||
nick: null,
|
||||
sasl: []
|
||||
nick: null
|
||||
},
|
||||
matrix: {
|
||||
instance: null,
|
||||
|
|
90
src/enums.js
90
src/enums.js
|
@ -40,8 +40,6 @@ export const ProxyPolicy = {
|
|||
export const Fetcher = {
|
||||
/** HTTP requests to ActivityPub */
|
||||
ACTIVITYPUB: 'activitypub',
|
||||
/** ASPE HTTP requests */
|
||||
ASPE: 'aspe',
|
||||
/** DNS module from Node.js */
|
||||
DNS: 'dns',
|
||||
/** GraphQL over HTTP requests */
|
||||
|
@ -52,8 +50,6 @@ export const Fetcher = {
|
|||
IRC: 'irc',
|
||||
/** HTTP request to Matrix API */
|
||||
MATRIX: 'matrix',
|
||||
/** HKP and WKS request for OpenPGP */
|
||||
OPENPGP: 'openpgp',
|
||||
/** HTTP request to Telegram API */
|
||||
TELEGRAM: 'telegram',
|
||||
/** XMPP module from Node.js */
|
||||
|
@ -79,9 +75,9 @@ export const EntityEncodingFormat = {
|
|||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const ProofAccessRestriction = {
|
||||
export const ProofAccess = {
|
||||
/** Any HTTP request will work */
|
||||
NONE: 'none',
|
||||
GENERIC: 'generic',
|
||||
/** CORS requests are denied */
|
||||
NOCORS: 'nocors',
|
||||
/** HTTP requests must contain API or access tokens */
|
||||
|
@ -115,7 +111,7 @@ export const ClaimFormat = {
|
|||
}
|
||||
|
||||
/**
|
||||
* How to find the proof inside the fetched data
|
||||
* How to find the claim inside the proof's JSON data
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
|
@ -131,83 +127,13 @@ export const ClaimRelation = {
|
|||
/**
|
||||
* Status of the Claim instance
|
||||
* @readonly
|
||||
* @enum {number}
|
||||
* @enum {string}
|
||||
*/
|
||||
export const ClaimStatus = {
|
||||
/** Claim has been initialized */
|
||||
INIT: 100,
|
||||
INIT: 'init',
|
||||
/** Claim has matched its URI to candidate claim definitions */
|
||||
MATCHED: 101,
|
||||
/** Claim was successfully verified */
|
||||
VERIFIED: 200,
|
||||
/** Claim was successfully verified using proxied data */
|
||||
VERIFIED_VIA_PROXY: 201,
|
||||
/** Unknown matching error */
|
||||
MATCHING_ERROR: 300,
|
||||
/** No matched service providers */
|
||||
NO_MATCHES: 301,
|
||||
/** Unknown matching error */
|
||||
VERIFICATION_ERROR: 400,
|
||||
/** No proof found in data returned by service providers */
|
||||
NO_PROOF_FOUND: 401
|
||||
}
|
||||
|
||||
/**
|
||||
* Profile type
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const ProfileType = {
|
||||
/** ASP profile */
|
||||
ASP: 'asp',
|
||||
/** OpenPGP profile */
|
||||
OPENPGP: 'openpgp'
|
||||
}
|
||||
|
||||
/**
|
||||
* Public key type
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const PublicKeyType = {
|
||||
EDDSA: 'eddsa',
|
||||
ES256: 'es256',
|
||||
OPENPGP: 'openpgp',
|
||||
UNKNOWN: 'unknown',
|
||||
NONE: 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* Public key format
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const PublicKeyEncoding = {
|
||||
PEM: 'pem',
|
||||
JWK: 'jwk',
|
||||
ARMORED_PGP: 'armored_pgp',
|
||||
NONE: 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* Method to fetch the public key
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const PublicKeyFetchMethod = {
|
||||
ASPE: 'aspe',
|
||||
HKP: 'hkp',
|
||||
WKD: 'wkd',
|
||||
HTTP: 'http',
|
||||
NONE: 'none'
|
||||
}
|
||||
|
||||
/**
|
||||
* Protocol to query OpenPGP public keys
|
||||
* @readonly
|
||||
* @enum {string}
|
||||
*/
|
||||
export const OpenPgpQueryProtocol = {
|
||||
HKP: 'hkp',
|
||||
WKD: 'wkd'
|
||||
MATCHED: 'matched',
|
||||
/** Claim has verified one or multiple candidate claim definitions */
|
||||
VERIFIED: 'verified'
|
||||
}
|
||||
|
|
|
@ -13,36 +13,27 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs using ActivityPub HTTP requests
|
||||
* @module fetcher/activitypub
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.activitypub.fn({ url: 'https://domain.example/@alice' });
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import isURL from 'validator/lib/isURL.js'
|
||||
import { isNode } from 'browser-or-node'
|
||||
import crypto from 'crypto'
|
||||
import { version } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.url - The URL of the account to verify
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<object>} The fetched ActivityPub object
|
||||
* @async
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.url - The URL of the account to verify
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {object} opts - Options used to enable the request
|
||||
* @param {object} opts.claims
|
||||
* @param {object} opts.claims.activitypub
|
||||
* @param {string} opts.claims.activitypub.url - The URL of the verifier account
|
||||
* @param {string} opts.claims.activitypub.privateKey - The private key to sign the request
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
let timeoutHandle
|
||||
|
@ -98,7 +89,8 @@ export async function fn (data, opts) {
|
|||
})()
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs from Profile obtained through ASPE
|
||||
* @module fetcher/aspe
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.aspe.fn({ aspeUri: 'aspe:domain.example:abc123def456' });
|
||||
*/
|
||||
import axios from 'axios'
|
||||
import isFQDN from 'validator/lib/isFQDN.js'
|
||||
import { version } from '../constants.js'
|
||||
import { parseProfileJws } from '../asp.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
const reURI = /^aspe:([a-zA-Z0-9.\-_]*):([a-zA-Z0-9]*)/
|
||||
|
||||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.aspeUri - ASPE URI of the targeted profile
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<object>} The fetched claims from an ASP profile
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
let timeoutHandle
|
||||
const timeoutPromise = new Promise((resolve, reject) => {
|
||||
timeoutHandle = setTimeout(
|
||||
() => reject(new Error('Request was timed out')),
|
||||
data.fetcherTimeout ? data.fetcherTimeout : timeout
|
||||
)
|
||||
})
|
||||
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
const match = data.aspeUri.match(reURI)
|
||||
|
||||
if (!data.aspeUri || !reURI.test(data.aspeUri) || !isFQDN(match[1])) {
|
||||
reject(new Error('No valid ASPE URI provided'))
|
||||
return
|
||||
}
|
||||
|
||||
const url = `https://${match[1]}/.well-known/aspe/id/${match[2].toUpperCase()}`
|
||||
|
||||
axios.get(url, {
|
||||
headers: {
|
||||
Accept: 'application/asp+jwt',
|
||||
'User-Agent': `doipjs/${version}`
|
||||
},
|
||||
validateStatus: (status) => status >= 200 && status < 400
|
||||
})
|
||||
.then(async res => await parseProfileJws(res.data, data.aspeUri))
|
||||
.then(profile =>
|
||||
profile.personas.flatMap(p => { return p.claims.map(c => c._uri) })
|
||||
)
|
||||
.then(res => {
|
||||
resolve({
|
||||
claims: res
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
reject(e)
|
||||
})
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
clearTimeout(timeoutHandle)
|
||||
})
|
||||
}
|
|
@ -13,33 +13,19 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs using DNS TXT records
|
||||
* @module fetcher/dns
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.dns.fn({ domain: 'domain.example' });
|
||||
*/
|
||||
|
||||
import { isBrowser } from 'browser-or-node'
|
||||
import dns from 'dns'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.domain - The targeted domain
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<object>} The fetched DNS records
|
||||
* @async
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.domain - The targeted domain
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
if (isBrowser) {
|
||||
|
@ -70,7 +56,8 @@ export async function fn (data, opts) {
|
|||
})
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,34 +13,20 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs using GraphQL queries
|
||||
* @module fetcher/graphql
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.graphql.fn({ url: 'https://domain.example/graphql/v2', query: '{ "query": "..." }' });
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import { version } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
/**
|
||||
* Execute a GraphQL query via HTTP request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.url - The URL pointing at the GraphQL HTTP endpoint
|
||||
* @param {string} data.query - The GraphQL query to fetch the data containing the proof
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<object>} The fetched GraphQL object
|
||||
* @async
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.url - The URL pointing at the GraphQL HTTP endpoint
|
||||
* @param {string} data.query - The GraphQL query to fetch the data containing the proof
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @returns {Promise<object|string>}
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
let timeoutHandle
|
||||
|
@ -82,7 +68,8 @@ export async function fn (data, opts) {
|
|||
})
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,35 +13,21 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs using HTTP requests
|
||||
* @module fetcher/http
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.http.fn({ url: 'https://domain.example/data.json', format: 'json' });
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import { ProofFormat } from '../enums.js'
|
||||
import { version } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.url - The URL pointing at targeted content
|
||||
* @param {string} data.format - The format of the targeted content
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<object|string>} The fetched JSON object or text
|
||||
* @async
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.url - The URL pointing at targeted content
|
||||
* @param {string} data.format - The format of the targeted content
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @returns {Promise<object|string>}
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
let timeoutHandle
|
||||
|
@ -97,7 +83,8 @@ export async function fn (data, opts) {
|
|||
}
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
|
|
@ -14,12 +14,10 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
export * as activitypub from './activitypub.js'
|
||||
export * as aspe from './aspe.js'
|
||||
export * as dns from './dns.js'
|
||||
export * as graphql from './graphql.js'
|
||||
export * as http from './http.js'
|
||||
export * as irc from './irc.js'
|
||||
export * as matrix from './matrix.js'
|
||||
export * as openpgp from './openpgp.js'
|
||||
export * as telegram from './telegram.js'
|
||||
export * as xmpp from './xmpp.js'
|
||||
|
|
|
@ -14,9 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
export * as activitypub from './activitypub.js'
|
||||
export * as aspe from './aspe.js'
|
||||
export * as graphql from './graphql.js'
|
||||
export * as http from './http.js'
|
||||
export * as matrix from './matrix.js'
|
||||
export * as openpgp from './openpgp.js'
|
||||
export * as telegram from './telegram.js'
|
||||
|
|
|
@ -13,34 +13,24 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs using IRC
|
||||
* @module fetcher/irc
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.irc.fn({ nick: 'alice', domain: 'domain.example' });
|
||||
*/
|
||||
|
||||
import irc from 'irc-upd'
|
||||
import isAscii from 'validator/lib/isAscii.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 20000
|
||||
*/
|
||||
export const timeout = 20000
|
||||
|
||||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.nick - The nick of the targeted account
|
||||
* @param {string} data.domain - The domain on which the targeted account is registered
|
||||
* @async
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.nick - The nick of the targeted account
|
||||
* @param {string} data.domain - The domain on which the targeted account is registered
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<Array<string>>} The fetched proofs from an IRC account
|
||||
* @param {object} opts - Options used to enable the request
|
||||
* @param {object} opts.claims
|
||||
* @param {object} opts.claims.irc
|
||||
* @param {string} opts.claims.irc.nick - The nick to be used by the library to log in
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
let timeoutHandle
|
||||
|
@ -59,27 +49,14 @@ export async function fn (data, opts) {
|
|||
}
|
||||
|
||||
try {
|
||||
// Add sasl-related config if the server matches
|
||||
const matchedSaslConfig = opts.claims.irc.sasl.find(saslConfig => data.domain.match(new RegExp(saslConfig.domainRegex)) !== null)
|
||||
const saslOptions = matchedSaslConfig
|
||||
? {
|
||||
sasl: true,
|
||||
userName: matchedSaslConfig.username,
|
||||
password: matchedSaslConfig.password
|
||||
}
|
||||
: {
|
||||
sasl: false
|
||||
}
|
||||
|
||||
const client = new irc.Client(data.domain, opts.claims.irc.nick, {
|
||||
port: 6697,
|
||||
secure: true,
|
||||
channels: [],
|
||||
showErrors: false,
|
||||
debug: false,
|
||||
...saslOptions
|
||||
debug: false
|
||||
})
|
||||
const reKey = /[a-zA-Z0-9\-_]+\s+:\s((?:openpgp4fpr|aspe):.*)/
|
||||
const reKey = /[a-zA-Z0-9\-_]+\s+:\s(openpgp4fpr:.*)/
|
||||
const reEnd = /End\sof\s.*\staxonomy./
|
||||
const keys = []
|
||||
|
||||
|
@ -103,7 +80,8 @@ export async function fn (data, opts) {
|
|||
}
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,36 +13,27 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs using Matrix messages
|
||||
* @module fetcher/matrix
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.matrix.fn({ eventId: '$abc123def456', roomId: '!dBfQZxCoGVmSTujfiv:matrix.org' });
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import isFQDN from 'validator/lib/isFQDN.js'
|
||||
import isAscii from 'validator/lib/isAscii.js'
|
||||
import { version } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.eventId - The identifier of the targeted post
|
||||
* @param {string} data.roomId - The identifier of the room containing the targeted post
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<object>} The fetched Matrix object
|
||||
* @async
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.eventId - The identifier of the targeted post
|
||||
* @param {string} data.roomId - The identifier of the room containing the targeted post
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {object} opts - Options used to enable the request
|
||||
* @param {object} opts.claims
|
||||
* @param {object} opts.claims.matrix
|
||||
* @param {string} opts.claims.matrix.instance - The server hostname on which the library can log in
|
||||
* @param {string} opts.claims.matrix.accessToken - The access token required to identify the library ({@link https://www.matrix.org/docs/guides/client-server-api|Matrix docs})
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
let timeoutHandle
|
||||
|
@ -81,7 +72,8 @@ export async function fn (data, opts) {
|
|||
})
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,131 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs from OpenPGP notations
|
||||
* @module fetcher/openpgp
|
||||
* @example
|
||||
* import { fetcher, enums as E } from 'doipjs';
|
||||
*
|
||||
* const hkpProtocol = E.OpenPgpQueryProtocol.HKP;
|
||||
* const hkpUrl = 'https://keys.openpgp.org/vks/v1/by-fingerprint/ABC123DEF456';
|
||||
* const hkpData = await fetcher.openpgp.fn({ url: hkpUrl, protocol: hkpProtocol });
|
||||
*
|
||||
* const wkdProtocol = E.OpenPgpQueryProtocol.WKD;
|
||||
* const wkdUrl = 'https://domain.example/.well-known/openpgpkey/hu/kei1q4tipxxu1yj79k9kfukdhfy631xe?l=alice';
|
||||
* const wkdData = await fetcher.openpgp.fn({ url: wkdUrl, protocol: wkdProtocol });
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import { readKey } from 'openpgp'
|
||||
import { OpenPgpQueryProtocol } from '../enums.js'
|
||||
import { version } from '../constants.js'
|
||||
import { parsePublicKey } from '../openpgp.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.url - The URL pointing at targeted content
|
||||
* @param {OpenPgpQueryProtocol} data.protocol - The protocol used to access the targeted content
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<object>} The fetched notations from an OpenPGP key
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
let timeoutHandle
|
||||
const timeoutPromise = new Promise((resolve, reject) => {
|
||||
timeoutHandle = setTimeout(
|
||||
() => reject(new Error('Request was timed out')),
|
||||
data.fetcherTimeout ? data.fetcherTimeout : timeout
|
||||
)
|
||||
})
|
||||
|
||||
const fetchPromise = new Promise((resolve, reject) => {
|
||||
if (!data.url) {
|
||||
reject(new Error('No valid URI provided'))
|
||||
return
|
||||
}
|
||||
|
||||
switch (data.protocol) {
|
||||
case OpenPgpQueryProtocol.HKP:
|
||||
axios.get(data.url, {
|
||||
headers: {
|
||||
Accept: 'application/pgp-keys',
|
||||
'User-Agent': `doipjs/${version}`
|
||||
},
|
||||
validateStatus: (status) => status >= 200 && status < 400
|
||||
})
|
||||
.then(res => res.data)
|
||||
.then(async data => await readKey({ armoredKey: data }))
|
||||
.then(async publicKey => await parsePublicKey(publicKey))
|
||||
.then(profile =>
|
||||
profile.personas.flatMap(p => { return p.claims.map(c => c._uri) })
|
||||
)
|
||||
.then(res => {
|
||||
resolve({
|
||||
notations: {
|
||||
'proof@ariadne.id': res
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
reject(e)
|
||||
})
|
||||
break
|
||||
case OpenPgpQueryProtocol.WKD:
|
||||
axios.get(data.url, {
|
||||
headers: {
|
||||
Accept: 'application/octet-stream',
|
||||
'User-Agent': `doipjs/${version}`
|
||||
},
|
||||
responseType: 'arraybuffer',
|
||||
validateStatus: (status) => status >= 200 && status < 400
|
||||
})
|
||||
.then(res => res.data)
|
||||
.then(async data => await readKey({ binaryKey: data }))
|
||||
.then(async publicKey => await parsePublicKey(publicKey))
|
||||
.then(profile =>
|
||||
profile.personas.flatMap(p => { return p.claims.map(c => c._uri) })
|
||||
)
|
||||
.then(res => {
|
||||
resolve({
|
||||
notations: {
|
||||
'proof@ariadne.id': res
|
||||
}
|
||||
})
|
||||
})
|
||||
.catch(e => {
|
||||
reject(e)
|
||||
})
|
||||
break
|
||||
default:
|
||||
reject(new Error('Unsupported OpenPGP query protocol'))
|
||||
break
|
||||
}
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
clearTimeout(timeoutHandle)
|
||||
})
|
||||
}
|
|
@ -13,35 +13,25 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs using Telegram groups
|
||||
* @module fetcher/telegram
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.telegram.fn({ user: 'alice', chat: 'alice_identity_proof' });
|
||||
*/
|
||||
|
||||
import axios from 'axios'
|
||||
import isAscii from 'validator/lib/isAscii.js'
|
||||
import { version } from '../constants.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.chat - Telegram public group name (slug)
|
||||
* @param {string} data.user - Telegram username
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<object|string>} The fetched Telegram object
|
||||
* @async
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.chat - Telegram public chat username
|
||||
* @param {string} data.user - Telegram user username
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {object} opts - Options used to enable the request
|
||||
* @param {object} opts.claims
|
||||
* @param {object} opts.claims.telegram
|
||||
* @param {string} opts.claims.telegram.token - The Telegram Bot API token
|
||||
* @returns {Promise<object|string>}
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
let timeoutHandle
|
||||
|
@ -109,7 +99,8 @@ export async function fn (data, opts) {
|
|||
})
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,40 +13,23 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Fetch proofs from XMPP accounts
|
||||
* @module fetcher/xmpp
|
||||
* @example
|
||||
* import { fetcher } from 'doipjs';
|
||||
* const data = await fetcher.xmpp.fn({ id: 'alice@domain.example' });
|
||||
*/
|
||||
|
||||
import { client, xml } from '@xmpp/client'
|
||||
import debug from '@xmpp/debug'
|
||||
import isFQDN from 'validator/lib/isFQDN.js'
|
||||
import isAscii from 'validator/lib/isAscii.js'
|
||||
|
||||
/**
|
||||
* Default timeout after which the fetch is aborted
|
||||
* @constant
|
||||
* @type {number}
|
||||
* @default 5000
|
||||
*/
|
||||
export const timeout = 5000
|
||||
|
||||
let xmpp = null
|
||||
let iqCaller = null
|
||||
|
||||
/**
|
||||
* Start the XMPP client
|
||||
* @ignore
|
||||
* @function
|
||||
* @param {import('../types').XmppClaimVerificationConfig} params - XMPP claim verification config
|
||||
* @returns {Promise<object>} The fetched proofs from an XMPP account
|
||||
*/
|
||||
const xmppStart = async (params) => {
|
||||
const xmppStart = async (service, username, password) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xmpp = client({ ...params })
|
||||
const xmpp = client({
|
||||
service,
|
||||
username,
|
||||
password
|
||||
})
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
debug(xmpp, true)
|
||||
}
|
||||
|
@ -64,11 +47,17 @@ const xmppStart = async (params) => {
|
|||
/**
|
||||
* Execute a fetch request
|
||||
* @function
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.id - The identifier of the targeted account
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||
* @returns {Promise<Array<string>>} The fetched proofs from an XMPP account
|
||||
* @async
|
||||
* @param {object} data - Data used in the request
|
||||
* @param {string} data.id - The identifier of the targeted account
|
||||
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||
* @param {object} opts - Options used to enable the request
|
||||
* @param {object} opts.claims
|
||||
* @param {object} opts.claims.xmpp
|
||||
* @param {string} opts.claims.xmpp.service - The server hostname on which the library can log in
|
||||
* @param {string} opts.claims.xmpp.username - The username used to log in
|
||||
* @param {string} opts.claims.xmpp.password - The password used to log in
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function fn (data, opts) {
|
||||
try {
|
||||
|
@ -80,7 +69,11 @@ export async function fn (data, opts) {
|
|||
}
|
||||
|
||||
if (!xmpp || xmpp.status !== 'online') {
|
||||
const xmppStartRes = await xmppStart(opts.claims.xmpp)
|
||||
const xmppStartRes = await xmppStart(
|
||||
opts.claims.xmpp.service,
|
||||
opts.claims.xmpp.username,
|
||||
opts.claims.xmpp.password
|
||||
)
|
||||
xmpp = xmppStartRes.xmpp
|
||||
iqCaller = xmppStartRes.iqCaller
|
||||
}
|
||||
|
@ -183,7 +176,8 @@ export async function fn (data, opts) {
|
|||
})()
|
||||
})
|
||||
|
||||
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
|
11
src/index.js
11
src/index.js
|
@ -13,23 +13,16 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @module doipjs
|
||||
* @license Apache-2.0
|
||||
*/
|
||||
export { Profile } from './profile.js'
|
||||
export { Persona } from './persona.js'
|
||||
export { Claim } from './claim.js'
|
||||
export { ServiceProvider } from './serviceProvider.js'
|
||||
export * as ServiceProviderDefinitions from './serviceProviders/index.js'
|
||||
export * as claimDefinitions from './claimDefinitions/index.js'
|
||||
export * as proofs from './proofs.js'
|
||||
export * as openpgp from './openpgp.js'
|
||||
export * as keys from './keys.js'
|
||||
export * as asp from './asp.js'
|
||||
export * as signatures from './signatures.js'
|
||||
export * as enums from './enums.js'
|
||||
export * as defaults from './defaults.js'
|
||||
export * as utils from './utils.js'
|
||||
export * as verifications from './verifications.js'
|
||||
export * as schemas from './schemas.js'
|
||||
export * as fetcher from './fetcher/index.js'
|
||||
|
|
|
@ -19,102 +19,90 @@ import { readKey, PublicKey } from 'openpgp'
|
|||
import HKP from '@openpgp/hkp-client'
|
||||
import WKD from '@openpgp/wkd-client'
|
||||
import { Claim } from './claim.js'
|
||||
import { ProfileType, PublicKeyEncoding, PublicKeyFetchMethod, PublicKeyType } from './enums.js'
|
||||
import { Profile } from './profile.js'
|
||||
import { Persona } from './persona.js'
|
||||
|
||||
/**
|
||||
* Functions related to OpenPGP Profiles
|
||||
* @module openpgp
|
||||
* Functions related to the fetching and handling of keys
|
||||
* @module keys
|
||||
*/
|
||||
|
||||
/**
|
||||
* Fetch a public key using keyservers
|
||||
* @function
|
||||
* @param {string} identifier - Fingerprint or email address
|
||||
* @param {string} [keyserverDomain] - Domain of the keyserver
|
||||
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||
* @param {string} identifier - Fingerprint or email address
|
||||
* @param {string} [keyserverDomain=keys.openpgp.org] - Domain of the keyserver
|
||||
* @returns {Promise<PublicKey>}
|
||||
* @example
|
||||
* const key1 = doip.keys.fetchHKP('alice@domain.tld');
|
||||
* const key2 = doip.keys.fetchHKP('123abc123abc');
|
||||
* const key3 = doip.keys.fetchHKP('123abc123abc', 'pgpkeys.eu');
|
||||
*/
|
||||
export async function fetchHKP (identifier, keyserverDomain = 'keys.openpgp.org') {
|
||||
const keyserverBaseUrl = `https://${keyserverDomain ?? 'keys.openpgp.org'}`
|
||||
export async function fetchHKP (identifier, keyserverDomain) {
|
||||
const keyserverBaseUrl = keyserverDomain
|
||||
? `https://${keyserverDomain}`
|
||||
: 'https://keys.openpgp.org'
|
||||
|
||||
// @ts-ignore
|
||||
const hkp = new HKP(keyserverBaseUrl)
|
||||
const lookupOpts = {
|
||||
query: identifier
|
||||
}
|
||||
|
||||
const publicKeyArmored = await hkp
|
||||
const publicKey = await hkp
|
||||
.lookup(lookupOpts)
|
||||
.catch((error) => {
|
||||
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
||||
})
|
||||
|
||||
if (!publicKeyArmored) {
|
||||
if (!publicKey) {
|
||||
throw new Error('Key does not exist or could not be fetched')
|
||||
}
|
||||
|
||||
const publicKey = await readKey({
|
||||
armoredKey: publicKeyArmored
|
||||
return await readKey({
|
||||
armoredKey: publicKey
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new Error(`Key could not be read (${error})`)
|
||||
})
|
||||
|
||||
const profile = await parsePublicKey(publicKey)
|
||||
profile.publicKey.fetch.method = PublicKeyFetchMethod.HKP
|
||||
profile.publicKey.fetch.query = identifier
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a public key using Web Key Directory
|
||||
* @function
|
||||
* @param {string} identifier - Identifier of format 'username@domain.tld`
|
||||
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||
* @returns {Promise<PublicKey>}
|
||||
* @example
|
||||
* const key = doip.keys.fetchWKD('alice@domain.tld');
|
||||
*/
|
||||
export async function fetchWKD (identifier) {
|
||||
// @ts-ignore
|
||||
const wkd = new WKD()
|
||||
const lookupOpts = {
|
||||
email: identifier
|
||||
}
|
||||
|
||||
const publicKeyBinary = await wkd
|
||||
const publicKey = await wkd
|
||||
.lookup(lookupOpts)
|
||||
.catch((/** @type {Error} */ error) => {
|
||||
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
||||
})
|
||||
|
||||
if (!publicKeyBinary) {
|
||||
if (!publicKey) {
|
||||
throw new Error('Key does not exist or could not be fetched')
|
||||
}
|
||||
|
||||
const publicKey = await readKey({
|
||||
binaryKey: publicKeyBinary
|
||||
return await readKey({
|
||||
binaryKey: publicKey
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new Error(`Key could not be read (${error})`)
|
||||
})
|
||||
|
||||
const profile = await parsePublicKey(publicKey)
|
||||
profile.publicKey.fetch.method = PublicKeyFetchMethod.WKD
|
||||
profile.publicKey.fetch.query = identifier
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a public key from Keybase
|
||||
* @function
|
||||
* @param {string} username - Keybase username
|
||||
* @param {string} fingerprint - Fingerprint of key
|
||||
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||
* @param {string} username - Keybase username
|
||||
* @param {string} fingerprint - Fingerprint of key
|
||||
* @returns {Promise<PublicKey>}
|
||||
* @example
|
||||
* const key = doip.keys.fetchKeybase('alice', '123abc123abc');
|
||||
*/
|
||||
|
@ -138,26 +126,19 @@ export async function fetchKeybase (username, fingerprint) {
|
|||
throw new Error(`Error fetching Keybase key: ${e.message}`)
|
||||
}
|
||||
|
||||
const publicKey = await readKey({
|
||||
return await readKey({
|
||||
armoredKey: rawKeyContent
|
||||
})
|
||||
.catch((error) => {
|
||||
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
||||
})
|
||||
|
||||
const profile = await parsePublicKey(publicKey)
|
||||
profile.publicKey.fetch.method = PublicKeyFetchMethod.HTTP
|
||||
profile.publicKey.fetch.query = null
|
||||
profile.publicKey.fetch.resolvedUrl = keyLink
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a public key from armored public key text data
|
||||
* Get a public key from plaintext data
|
||||
* @function
|
||||
* @param {string} rawKeyContent - Plaintext ASCII-formatted public key data
|
||||
* @returns {Promise<Profile>} The profile from the armored public key
|
||||
* @returns {Promise<PublicKey>}
|
||||
* @example
|
||||
* const plainkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
*
|
||||
|
@ -175,16 +156,14 @@ export async function fetchPlaintext (rawKeyContent) {
|
|||
throw new Error(`Key could not be read (${error})`)
|
||||
})
|
||||
|
||||
const profile = await parsePublicKey(publicKey)
|
||||
|
||||
return profile
|
||||
return publicKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a public key using an URI
|
||||
* @function
|
||||
* @param {string} uri - URI that defines the location of the key
|
||||
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||
* @returns {Promise<PublicKey>}
|
||||
* @example
|
||||
* const key1 = doip.keys.fetchURI('hkp:alice@domain.tld');
|
||||
* const key2 = doip.keys.fetchURI('hkp:123abc123abc');
|
||||
|
@ -230,7 +209,7 @@ export async function fetchURI (uri) {
|
|||
* This function will also try and parse the input as a plaintext key
|
||||
* @function
|
||||
* @param {string} identifier - URI that defines the location of the key
|
||||
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||
* @returns {Promise<PublicKey>}
|
||||
* @example
|
||||
* const key1 = doip.keys.fetch('alice@domain.tld');
|
||||
* const key2 = doip.keys.fetch('123abc123abc');
|
||||
|
@ -239,48 +218,50 @@ export async function fetch (identifier) {
|
|||
const re = /([a-zA-Z0-9@._=+-]*)(?::([a-zA-Z0-9@._=+-]*))?/
|
||||
const match = identifier.match(re)
|
||||
|
||||
let profile = null
|
||||
let pubKey = null
|
||||
|
||||
// Attempt plaintext
|
||||
try {
|
||||
profile = await fetchPlaintext(identifier)
|
||||
} catch (e) {}
|
||||
if (!pubKey) {
|
||||
try {
|
||||
pubKey = await fetchPlaintext(identifier)
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Attempt WKD
|
||||
if (!profile && identifier.includes('@')) {
|
||||
if (!pubKey && identifier.includes('@')) {
|
||||
try {
|
||||
profile = await fetchWKD(match[1])
|
||||
pubKey = await fetchWKD(match[1])
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
// Attempt HKP
|
||||
if (!profile) {
|
||||
profile = await fetchHKP(
|
||||
if (!pubKey) {
|
||||
pubKey = await fetchHKP(
|
||||
match[2] ? match[2] : match[1],
|
||||
match[2] ? match[1] : null
|
||||
)
|
||||
}
|
||||
|
||||
if (!profile) {
|
||||
if (!pubKey) {
|
||||
throw new Error('Key does not exist or could not be fetched')
|
||||
}
|
||||
|
||||
return profile
|
||||
return pubKey
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a public key to get a profile
|
||||
* Process a public key to get user data and claims
|
||||
* @function
|
||||
* @param {PublicKey} publicKey - The public key to parse
|
||||
* @returns {Promise<Profile>} The profile from the processed OpenPGP key
|
||||
* @param {PublicKey} publicKey - The public key to process
|
||||
* @returns {Promise<object>}
|
||||
* @example
|
||||
* const key = doip.keys.fetchURI('hkp:alice@domain.tld');
|
||||
* const profile = doip.keys.parsePublicKey(key);
|
||||
* profile.personas[0].claims.forEach(claim => {
|
||||
* const data = doip.keys.process(key);
|
||||
* data.users[0].claims.forEach(claim => {
|
||||
* console.log(claim.uri);
|
||||
* });
|
||||
*/
|
||||
export async function parsePublicKey (publicKey) {
|
||||
export async function process (publicKey) {
|
||||
if (!(publicKey && (publicKey instanceof PublicKey))) {
|
||||
throw new Error('Invalid public key')
|
||||
}
|
||||
|
@ -288,45 +269,47 @@ export async function parsePublicKey (publicKey) {
|
|||
const fingerprint = publicKey.getFingerprint()
|
||||
const primaryUser = await publicKey.getPrimaryUser()
|
||||
const users = publicKey.users
|
||||
const personas = []
|
||||
const usersOutput = []
|
||||
|
||||
users.forEach((user, i) => {
|
||||
if (!user.userID) return
|
||||
|
||||
const pe = new Persona(user.userID.name, [])
|
||||
pe.setIdentifier(user.userID.userID)
|
||||
pe.setDescription(user.userID.comment)
|
||||
pe.setEmailAddress(user.userID.email)
|
||||
usersOutput[i] = {
|
||||
userData: {
|
||||
id: user.userID ? user.userID.userID : null,
|
||||
name: user.userID ? user.userID.name : null,
|
||||
email: user.userID ? user.userID.email : null,
|
||||
comment: user.userID ? user.userID.comment : null,
|
||||
isPrimary: primaryUser.index === i,
|
||||
isRevoked: false
|
||||
},
|
||||
claims: []
|
||||
}
|
||||
|
||||
if ('selfCertifications' in user && user.selfCertifications.length > 0) {
|
||||
const selfCertification = user.selfCertifications.sort((e1, e2) => e2.created.getTime() - e1.created.getTime())[0]
|
||||
|
||||
if (selfCertification.revoked) {
|
||||
pe.revoke()
|
||||
}
|
||||
const notations = selfCertification.rawNotations
|
||||
pe.claims = notations
|
||||
usersOutput[i].claims = notations
|
||||
.filter(
|
||||
({ name, humanReadable }) =>
|
||||
humanReadable && (name === 'proof@ariadne.id' || name === 'proof@metacode.biz')
|
||||
)
|
||||
.map(
|
||||
({ value }) =>
|
||||
new Claim(new TextDecoder().decode(value), `openpgp4fpr:${fingerprint}`)
|
||||
new Claim(new TextDecoder().decode(value), fingerprint)
|
||||
)
|
||||
}
|
||||
|
||||
personas.push(pe)
|
||||
usersOutput[i].userData.isRevoked = selfCertification.revoked
|
||||
}
|
||||
})
|
||||
|
||||
const profile = new Profile(ProfileType.OPENPGP, `openpgp4fpr:${fingerprint}`, personas)
|
||||
profile.primaryPersonaIndex = primaryUser.index
|
||||
|
||||
profile.publicKey.keyType = PublicKeyType.OPENPGP
|
||||
profile.publicKey.fingerprint = fingerprint
|
||||
profile.publicKey.encoding = PublicKeyEncoding.ARMORED_PGP
|
||||
profile.publicKey.encodedKey = publicKey.armor()
|
||||
profile.publicKey.key = publicKey
|
||||
|
||||
return profile
|
||||
return {
|
||||
fingerprint,
|
||||
users: usersOutput,
|
||||
primaryUserIndex: primaryUser.index,
|
||||
key: {
|
||||
data: publicKey,
|
||||
fetchMethod: null,
|
||||
uri: null
|
||||
}
|
||||
}
|
||||
}
|
193
src/persona.js
193
src/persona.js
|
@ -13,191 +13,42 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// eslint-disable-next-line
|
||||
import { Claim } from './claim.js'
|
||||
|
||||
/**
|
||||
* A persona with identity claims
|
||||
* @class
|
||||
* @classdesc A persona with identity claims
|
||||
* @constructor
|
||||
* @public
|
||||
* @example
|
||||
* const claim = Claim('https://alice.tld', '123');
|
||||
* const pers = Persona('Alice', 'About Alice', [claim]);
|
||||
*/
|
||||
export class Persona {
|
||||
/**
|
||||
* @param {string} name - Name of the persona
|
||||
* @param {Array<Claim>} claims - Claims of the persona
|
||||
*/
|
||||
constructor (name, claims) {
|
||||
/**
|
||||
* Identifier of the persona
|
||||
* @type {string | null}
|
||||
* @public
|
||||
* @param {string} name
|
||||
* @param {string} [description]
|
||||
* @param {Claim[]} [claims]
|
||||
*/
|
||||
this.identifier = null
|
||||
constructor (name, description, claims) {
|
||||
/**
|
||||
* Name to be displayed on the profile page
|
||||
* @type {string}
|
||||
* @public
|
||||
*/
|
||||
* Name to be displayed on the profile page
|
||||
* @type {string}
|
||||
* @public
|
||||
*/
|
||||
this.name = name
|
||||
/**
|
||||
* Email address of the persona
|
||||
* @type {string | null}
|
||||
* @public
|
||||
*/
|
||||
this.email = null
|
||||
/**
|
||||
* Description to be displayed on the profile page
|
||||
* @type {string | null}
|
||||
* @public
|
||||
*/
|
||||
this.description = null
|
||||
/**
|
||||
* URL to an avatar image
|
||||
* @type {string | null}
|
||||
* @public
|
||||
*/
|
||||
this.avatarUrl = null
|
||||
/**
|
||||
* Theme color
|
||||
* @type {string | null}
|
||||
* @public
|
||||
*/
|
||||
this.themeColor = null
|
||||
/**
|
||||
* List of identity claims
|
||||
* @type {Array<Claim>}
|
||||
* @public
|
||||
*/
|
||||
this.claims = claims
|
||||
/**
|
||||
* Has the persona been revoked
|
||||
* @type {boolean}
|
||||
* @public
|
||||
*/
|
||||
this.isRevoked = false
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JSON object and convert it into a persona
|
||||
* @function
|
||||
* @param {object} personaObject - JSON representation of a persona
|
||||
* @param {number} profileVersion - Version of the Profile containing the persona
|
||||
* @returns {Persona | Error} Parsed persona
|
||||
* @example
|
||||
* doip.Persona.fromJSON(JSON.stringify(persona), 2);
|
||||
*/
|
||||
static fromJSON (personaObject, profileVersion) {
|
||||
/** @type {Persona} */
|
||||
let persona
|
||||
let result
|
||||
|
||||
if (typeof personaObject === 'object' && profileVersion) {
|
||||
switch (profileVersion) {
|
||||
case 2:
|
||||
result = importJsonPersonaVersion2(personaObject)
|
||||
if (result instanceof Error) {
|
||||
throw result
|
||||
}
|
||||
persona = result
|
||||
break
|
||||
|
||||
default:
|
||||
throw new Error('Invalid persona version')
|
||||
}
|
||||
}
|
||||
|
||||
return persona
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the persona's identifier
|
||||
* @function
|
||||
* @param {string} identifier - Identifier of the persona
|
||||
*/
|
||||
setIdentifier (identifier) {
|
||||
this.identifier = identifier
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the persona's description
|
||||
* @function
|
||||
* @param {string} description - Description of the persona
|
||||
*/
|
||||
setDescription (description) {
|
||||
* Description to be displayed on the profile page
|
||||
* @type {string}
|
||||
* @public
|
||||
*/
|
||||
this.description = description
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the persona's email address
|
||||
* @function
|
||||
* @param {string} email - Email address of the persona
|
||||
*/
|
||||
setEmailAddress (email) {
|
||||
this.email = email
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the URL to the persona's avatar
|
||||
* @function
|
||||
* @param {string} avatarUrl - URL to the persona's avatar
|
||||
*/
|
||||
setAvatarUrl (avatarUrl) {
|
||||
this.avatarUrl = avatarUrl
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a claim
|
||||
* @function
|
||||
* @param {Claim} claim - Claim to add
|
||||
*/
|
||||
addClaim (claim) {
|
||||
this.claims.push(claim)
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke the persona
|
||||
* @function
|
||||
*/
|
||||
revoke () {
|
||||
this.isRevoked = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the persona
|
||||
* @function
|
||||
* @returns {object} JSON representation of the persona
|
||||
*/
|
||||
toJSON () {
|
||||
return {
|
||||
identifier: this.identifier,
|
||||
name: this.name,
|
||||
email: this.email,
|
||||
description: this.description,
|
||||
avatarUrl: this.avatarUrl,
|
||||
themeColor: this.themeColor,
|
||||
isRevoked: this.isRevoked,
|
||||
claims: this.claims.map(x => x.toJSON())
|
||||
}
|
||||
/**
|
||||
* List of identity claims
|
||||
* @type {Array<Claim>}
|
||||
* @public
|
||||
*/
|
||||
this.claims = claims
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param {object} personaObject - JSON representation of a persona
|
||||
* @returns {Persona | Error} Parsed persona
|
||||
*/
|
||||
function importJsonPersonaVersion2 (personaObject) {
|
||||
const claims = personaObject.claims.map(x => Claim.fromJSON(x))
|
||||
|
||||
const persona = new Persona(personaObject.name, claims)
|
||||
|
||||
persona.identifier = personaObject.identifier
|
||||
persona.email = personaObject.email
|
||||
persona.description = personaObject.description
|
||||
persona.avatarUrl = personaObject.avatarUrl
|
||||
persona.themeColor = personaObject.avatarUrl
|
||||
persona.isRevoked = personaObject.isRevoked
|
||||
|
||||
return persona
|
||||
}
|
||||
|
|
162
src/profile.js
162
src/profile.js
|
@ -13,13 +13,14 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { PublicKeyFetchMethod, PublicKeyEncoding, PublicKeyType, ProfileType } from './enums.js'
|
||||
// eslint-disable-next-line
|
||||
import { Persona } from './persona.js'
|
||||
|
||||
/**
|
||||
* @class
|
||||
* @classdesc A profile of personas with identity claims
|
||||
* @param {Array<Persona>} personas - Personas of the profile
|
||||
* A profile of personas with identity claims
|
||||
* @function
|
||||
* @param {Array<Persona>} personas
|
||||
* @public
|
||||
* @example
|
||||
* const claim = Claim('https://alice.tld', '123');
|
||||
* const pers = Persona('Alice', 'About Alice', [claim]);
|
||||
|
@ -27,150 +28,23 @@ import { Persona } from './persona.js'
|
|||
*/
|
||||
export class Profile {
|
||||
/**
|
||||
* Create a new profile
|
||||
* @function
|
||||
* @param {ProfileType} profileType - Type of profile (ASP, OpenPGP, etc.)
|
||||
* @param {string} identifier - Profile identifier (fingerprint, URI, etc.)
|
||||
* @param {Array<Persona>} personas - Personas of the profile
|
||||
* @public
|
||||
*/
|
||||
constructor (profileType, identifier, personas) {
|
||||
this.profileVersion = 2
|
||||
/**
|
||||
* Profile version
|
||||
* @type {ProfileType}
|
||||
* Create a new profile
|
||||
* @function
|
||||
* @param {Array<Persona>} personas
|
||||
* @public
|
||||
*/
|
||||
this.profileType = profileType
|
||||
constructor (personas) {
|
||||
/**
|
||||
* Identifier of the profile (fingerprint, email address, uri...)
|
||||
* @type {string}
|
||||
* @public
|
||||
*/
|
||||
this.identifier = identifier
|
||||
/**
|
||||
* List of personas
|
||||
* @type {Array<Persona>}
|
||||
* @public
|
||||
*/
|
||||
* List of personas
|
||||
* @type {Array<Persona>}
|
||||
* @public
|
||||
*/
|
||||
this.personas = personas || []
|
||||
/**
|
||||
* Index of primary persona (to be displayed first or prominently)
|
||||
* @type {number}
|
||||
* @public
|
||||
*/
|
||||
this.primaryPersonaIndex = personas.length > 0 ? 0 : -1
|
||||
/**
|
||||
* The cryptographic key associated with the profile
|
||||
* @type {import('./types').ProfilePublicKey}
|
||||
* @public
|
||||
*/
|
||||
this.publicKey = {
|
||||
keyType: PublicKeyType.NONE,
|
||||
fingerprint: null,
|
||||
encoding: PublicKeyEncoding.NONE,
|
||||
encodedKey: null,
|
||||
key: null,
|
||||
fetch: {
|
||||
method: PublicKeyFetchMethod.NONE,
|
||||
query: null,
|
||||
resolvedUrl: null
|
||||
}
|
||||
}
|
||||
/**
|
||||
* List of verifier URLs
|
||||
* @type {Array<import('./types').ProfileVerifier>}
|
||||
* @public
|
||||
*/
|
||||
this.verifiers = []
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a JSON object and convert it into a profile
|
||||
* @function
|
||||
* @param {object} profileObject - JSON representation of a profile
|
||||
* @returns {Profile | Error} Parsed profile
|
||||
* @example
|
||||
* doip.Profile.fromJSON(JSON.stringify(profile));
|
||||
*/
|
||||
static fromJSON (profileObject) {
|
||||
/** @type {Profile} */
|
||||
let profile
|
||||
let result
|
||||
|
||||
if (typeof profileObject === 'object' && 'profileVersion' in profileObject) {
|
||||
switch (profileObject.profileVersion) {
|
||||
case 2:
|
||||
result = importJsonProfileVersion2(profileObject)
|
||||
if (result instanceof Error) {
|
||||
throw result
|
||||
}
|
||||
profile = result
|
||||
break
|
||||
|
||||
default:
|
||||
throw new Error('Invalid profile version')
|
||||
}
|
||||
}
|
||||
|
||||
return profile
|
||||
}
|
||||
|
||||
/**
|
||||
* Add profile verifier to the profile
|
||||
* @function
|
||||
* @param {string} name - Name of the verifier
|
||||
* @param {string} url - URL of the verifier
|
||||
*/
|
||||
addVerifier (name, url) {
|
||||
this.verifiers.push({ name, url })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the profile
|
||||
* @function
|
||||
* @returns {object} JSON representation of the profile
|
||||
*/
|
||||
toJSON () {
|
||||
return {
|
||||
profileVersion: this.profileVersion,
|
||||
profileType: this.profileType,
|
||||
identifier: this.identifier,
|
||||
personas: this.personas.map(x => x.toJSON()),
|
||||
primaryPersonaIndex: this.primaryPersonaIndex,
|
||||
publicKey: {
|
||||
keyType: this.publicKey.keyType,
|
||||
fingerprint: this.publicKey.fingerprint,
|
||||
encoding: this.publicKey.encoding,
|
||||
encodedKey: this.publicKey.encodedKey,
|
||||
fetch: {
|
||||
method: this.publicKey.fetch.method,
|
||||
query: this.publicKey.fetch.query,
|
||||
resolvedUrl: this.publicKey.fetch.resolvedUrl
|
||||
}
|
||||
},
|
||||
verifiers: this.verifiers
|
||||
}
|
||||
* Index of primary persona (to be displayed first or prominently)
|
||||
* @type {Number}
|
||||
* @public
|
||||
*/
|
||||
this.primaryPersona = -1
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* @param {object} profileObject - JSON representation of the profile
|
||||
* @returns {Profile | Error} Parsed profile
|
||||
*/
|
||||
function importJsonProfileVersion2 (profileObject) {
|
||||
if (!('profileVersion' in profileObject && profileObject.profileVersion === 2)) {
|
||||
return new Error('Invalid profile')
|
||||
}
|
||||
|
||||
const personas = profileObject.personas.map(x => Persona.fromJSON(x, 2))
|
||||
|
||||
const profile = new Profile(profileObject.profileType, profileObject.identifier, personas)
|
||||
|
||||
profile.primaryPersonaIndex = profileObject.primaryPersonaIndex
|
||||
profile.publicKey = profileObject.publicKey
|
||||
profile.verifiers = profileObject.verifiers
|
||||
|
||||
return profile
|
||||
}
|
||||
|
|
|
@ -14,10 +14,9 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
import { isNode } from 'browser-or-node'
|
||||
import { fetcher } from './index.js'
|
||||
import * as fetcher from './fetcher/index.js'
|
||||
import { generateProxyURL } from './utils.js'
|
||||
import { ProxyPolicy, ProofAccessRestriction } from './enums.js'
|
||||
import { ServiceProvider } from './serviceProvider.js'
|
||||
import { Fetcher, ProxyPolicy, ProofAccess } from './enums.js'
|
||||
|
||||
/**
|
||||
* @module proofs
|
||||
|
@ -29,11 +28,21 @@ import { ServiceProvider } from './serviceProvider.js'
|
|||
* the `data` parameter and the proxy policy set in the `opts` parameter to
|
||||
* choose the right approach to fetch the proof. An error will be thrown if no
|
||||
* approach is possible.
|
||||
* @param {ServiceProvider} data - Data from a claim definition
|
||||
* @param {import('./types').VerificationConfig} opts - Options to enable the request
|
||||
* @returns {Promise<object|string>} Fetched proof data
|
||||
* @async
|
||||
* @param {object} data - Data from a claim definition
|
||||
* @param {object} opts - Options to enable the request
|
||||
* @returns {Promise<object|string>}
|
||||
*/
|
||||
export async function fetch (data, opts) {
|
||||
switch (data.proof.request.fetcher) {
|
||||
case Fetcher.HTTP:
|
||||
data.proof.request.data.format = data.proof.request.format
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
if (isNode) {
|
||||
return handleNodeRequests(data, opts)
|
||||
}
|
||||
|
@ -41,23 +50,18 @@ export async function fetch (data, opts) {
|
|||
return handleBrowserRequests(data, opts)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ServiceProvider} data - Data from a claim definition
|
||||
* @param {object} opts - Options to enable the request
|
||||
* @returns {Promise<object|string>} Fetched proof data
|
||||
*/
|
||||
const handleBrowserRequests = (data, opts) => {
|
||||
switch (opts.proxy.policy) {
|
||||
case ProxyPolicy.ALWAYS:
|
||||
return createProxyRequestPromise(data, opts)
|
||||
|
||||
case ProxyPolicy.NEVER:
|
||||
switch (data.proof.request.accessRestriction) {
|
||||
case ProofAccessRestriction.NONE:
|
||||
case ProofAccessRestriction.GRANTED:
|
||||
switch (data.proof.request.access) {
|
||||
case ProofAccess.GENERIC:
|
||||
case ProofAccess.GRANTED:
|
||||
return createDefaultRequestPromise(data, opts)
|
||||
case ProofAccessRestriction.NOCORS:
|
||||
case ProofAccessRestriction.SERVER:
|
||||
case ProofAccess.NOCORS:
|
||||
case ProofAccess.SERVER:
|
||||
throw new Error(
|
||||
'Impossible to fetch proof (bad combination of service access and proxy policy)'
|
||||
)
|
||||
|
@ -66,14 +70,14 @@ const handleBrowserRequests = (data, opts) => {
|
|||
}
|
||||
|
||||
case ProxyPolicy.ADAPTIVE:
|
||||
switch (data.proof.request.accessRestriction) {
|
||||
case ProofAccessRestriction.NONE:
|
||||
switch (data.proof.request.access) {
|
||||
case ProofAccess.GENERIC:
|
||||
return createFallbackRequestPromise(data, opts)
|
||||
case ProofAccessRestriction.NOCORS:
|
||||
case ProofAccess.NOCORS:
|
||||
return createProxyRequestPromise(data, opts)
|
||||
case ProofAccessRestriction.GRANTED:
|
||||
case ProofAccess.GRANTED:
|
||||
return createFallbackRequestPromise(data, opts)
|
||||
case ProofAccessRestriction.SERVER:
|
||||
case ProofAccess.SERVER:
|
||||
return createProxyRequestPromise(data, opts)
|
||||
default:
|
||||
throw new Error('Invalid proof access value')
|
||||
|
@ -84,11 +88,6 @@ const handleBrowserRequests = (data, opts) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ServiceProvider} data - Data from a claim definition
|
||||
* @param {object} opts - Options to enable the request
|
||||
* @returns {Promise<object|string>} Fetched proof data
|
||||
*/
|
||||
const handleNodeRequests = (data, opts) => {
|
||||
switch (opts.proxy.policy) {
|
||||
case ProxyPolicy.ALWAYS:
|
||||
|
@ -105,16 +104,8 @@ const handleNodeRequests = (data, opts) => {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ServiceProvider} data - Data from a claim definition
|
||||
* @param {object} opts - Options to enable the request
|
||||
* @returns {Promise<object|string>} Fetched proof data
|
||||
*/
|
||||
const createDefaultRequestPromise = (data, opts) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!(data.proof.request.fetcher in fetcher)) {
|
||||
reject(new Error(`fetcher for ${data.proof.request.fetcher} not found`))
|
||||
}
|
||||
fetcher[data.proof.request.fetcher]
|
||||
.fn(data.proof.request.data, opts)
|
||||
.then((res) => {
|
||||
|
@ -131,11 +122,6 @@ const createDefaultRequestPromise = (data, opts) => {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ServiceProvider} data - Data from a claim definition
|
||||
* @param {object} opts - Options to enable the request
|
||||
* @returns {Promise<object|string>} Fetched proof data
|
||||
*/
|
||||
const createProxyRequestPromise = (data, opts) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
let proxyUrl
|
||||
|
@ -151,8 +137,8 @@ const createProxyRequestPromise = (data, opts) => {
|
|||
|
||||
const requestData = {
|
||||
url: proxyUrl,
|
||||
format: data.proof.response.format,
|
||||
fetcherTimeout: data.proof.request.fetcher in fetcher ? fetcher[data.proof.request.fetcher].timeout : 30000
|
||||
format: data.proof.request.format,
|
||||
fetcherTimeout: fetcher[data.proof.request.fetcher].timeout
|
||||
}
|
||||
fetcher.http
|
||||
.fn(requestData, opts)
|
||||
|
@ -170,11 +156,6 @@ const createProxyRequestPromise = (data, opts) => {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {ServiceProvider} data - Data from a claim definition
|
||||
* @param {object} opts - Options to enable the request
|
||||
* @returns {Promise<object|string>} Fetched proof data
|
||||
*/
|
||||
const createFallbackRequestPromise = (data, opts) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
createDefaultRequestPromise(data, opts)
|
||||
|
|
373
src/schemas.js
373
src/schemas.js
|
@ -1,373 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
export const profile = {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
$id: 'https://spec.keyoxide.org/2/profile.schema.json',
|
||||
title: 'Profile',
|
||||
description: 'Keyoxide profile with personas',
|
||||
type: 'object',
|
||||
properties: {
|
||||
profileVersion: {
|
||||
description: 'The version of the profile',
|
||||
type: 'integer'
|
||||
},
|
||||
profileType: {
|
||||
description: 'The type of the profile [openpgp, asp]',
|
||||
type: 'string'
|
||||
},
|
||||
identifier: {
|
||||
description: 'Identifier of the profile (email, fingerprint, URI)',
|
||||
type: 'string'
|
||||
},
|
||||
personas: {
|
||||
description: 'The personas inside the profile',
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: 'https://spec.keyoxide.org/2/persona.schema.json'
|
||||
},
|
||||
minItems: 1,
|
||||
uniqueItems: true
|
||||
},
|
||||
primaryPersonaIndex: {
|
||||
description: 'The index of the primary persona',
|
||||
type: 'integer'
|
||||
},
|
||||
publicKey: {
|
||||
description: 'The cryptographic key associated with the profile',
|
||||
type: 'object',
|
||||
properties: {
|
||||
keyType: {
|
||||
description: 'The type of cryptographic key [eddsa, es256, openpgp, none]',
|
||||
type: 'string'
|
||||
},
|
||||
encoding: {
|
||||
description: 'The encoding of the cryptographic key [pem, jwk, armored_pgp, none]',
|
||||
type: 'string'
|
||||
},
|
||||
encodedKey: {
|
||||
description: 'The encoded cryptographic key (PEM, stringified JWK, ...)',
|
||||
type: ['string', 'null']
|
||||
},
|
||||
fetch: {
|
||||
description: 'Details on how to fetch the public key',
|
||||
type: 'object',
|
||||
properties: {
|
||||
method: {
|
||||
description: 'The method to fetch the key [aspe, hkp, wkd, http, none]',
|
||||
type: 'string'
|
||||
},
|
||||
query: {
|
||||
description: 'The query to fetch the key',
|
||||
type: ['string', 'null']
|
||||
},
|
||||
resolvedUrl: {
|
||||
description: 'The URL the method eventually resolved to',
|
||||
type: ['string', 'null']
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
'keyType',
|
||||
'fetch'
|
||||
]
|
||||
},
|
||||
verifiers: {
|
||||
description: 'A list of links to verifiers',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
description: 'Name of the verifier site',
|
||||
type: 'string'
|
||||
},
|
||||
url: {
|
||||
description: 'URL to the profile page on the verifier site',
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
uniqueItems: true
|
||||
}
|
||||
},
|
||||
required: [
|
||||
'profileVersion',
|
||||
'profileType',
|
||||
'identifier',
|
||||
'personas',
|
||||
'primaryPersonaIndex',
|
||||
'publicKey',
|
||||
'verifiers'
|
||||
],
|
||||
additionalProperties: false
|
||||
}
|
||||
|
||||
export const persona = {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
$id: 'https://spec.keyoxide.org/2/persona.schema.json',
|
||||
title: 'Profile',
|
||||
description: 'Keyoxide persona with identity claims',
|
||||
type: 'object',
|
||||
properties: {
|
||||
identifier: {
|
||||
description: 'Identifier of the persona',
|
||||
type: ['string', 'null']
|
||||
},
|
||||
name: {
|
||||
description: 'Name of the persona',
|
||||
type: 'string'
|
||||
},
|
||||
email: {
|
||||
description: 'Email address of the persona',
|
||||
type: ['string', 'null']
|
||||
},
|
||||
description: {
|
||||
description: 'Description of the persona',
|
||||
type: ['string', 'null']
|
||||
},
|
||||
avatarUrl: {
|
||||
description: 'URL to an avatar image',
|
||||
type: ['string', 'null']
|
||||
},
|
||||
themeColor: {
|
||||
description: 'Profile page theme color',
|
||||
type: ['string', 'null']
|
||||
},
|
||||
isRevoked: {
|
||||
type: 'boolean'
|
||||
},
|
||||
claims: {
|
||||
description: 'A list of identity claims',
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: 'https://spec.keyoxide.org/2/claim.schema.json'
|
||||
},
|
||||
uniqueItems: true
|
||||
}
|
||||
},
|
||||
required: [
|
||||
'name',
|
||||
'claims'
|
||||
],
|
||||
additionalProperties: false
|
||||
}
|
||||
|
||||
export const claim = {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
$id: 'https://spec.keyoxide.org/2/claim.schema.json',
|
||||
title: 'Identity claim',
|
||||
description: 'Verifiable online identity claim',
|
||||
type: 'object',
|
||||
properties: {
|
||||
claimVersion: {
|
||||
description: 'The version of the claim',
|
||||
type: 'integer'
|
||||
},
|
||||
uri: {
|
||||
description: 'The claim URI',
|
||||
type: 'string'
|
||||
},
|
||||
proofs: {
|
||||
description: 'The proofs that would verify the claim',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
minItems: 1,
|
||||
uniqueItems: true
|
||||
},
|
||||
matches: {
|
||||
description: 'Service providers matched to the claim',
|
||||
type: 'array',
|
||||
items: {
|
||||
$ref: 'https://spec.keyoxide.org/2/serviceprovider.schema.json'
|
||||
},
|
||||
uniqueItems: true
|
||||
},
|
||||
status: {
|
||||
type: 'integer',
|
||||
description: 'Claim status code'
|
||||
},
|
||||
display: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
profileName: {
|
||||
type: 'string',
|
||||
description: 'Account name to display in the user interface'
|
||||
},
|
||||
profileUrl: {
|
||||
type: ['string', 'null'],
|
||||
description: 'Profile URL to link to in the user interface'
|
||||
},
|
||||
proofUrl: {
|
||||
type: ['string', 'null'],
|
||||
description: 'Proof URL to link to in the user interface'
|
||||
},
|
||||
serviceProviderName: {
|
||||
type: ['string', 'null'],
|
||||
description: 'Name of the service provider to display in the user interface'
|
||||
},
|
||||
serviceProviderId: {
|
||||
type: ['string', 'null'],
|
||||
description: 'Id of the service provider'
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
'claimVersion',
|
||||
'uri',
|
||||
'proofs',
|
||||
'status',
|
||||
'display'
|
||||
],
|
||||
additionalProperties: false
|
||||
}
|
||||
|
||||
export const serviceprovider = {
|
||||
$schema: 'https://json-schema.org/draft/2020-12/schema',
|
||||
$id: 'https://spec.keyoxide.org/2/serviceprovider.schema.json',
|
||||
title: 'Service provider',
|
||||
description: 'A service provider that can be matched to identity claims',
|
||||
type: 'object',
|
||||
properties: {
|
||||
about: {
|
||||
description: 'Details about the service provider',
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: {
|
||||
description: 'Full name of the service provider',
|
||||
type: 'string'
|
||||
},
|
||||
id: {
|
||||
description: 'Identifier of the service provider (no whitespace or symbols, lowercase)',
|
||||
type: 'string'
|
||||
},
|
||||
homepage: {
|
||||
description: 'URL to the homepage of the service provider',
|
||||
type: ['string', 'null']
|
||||
}
|
||||
}
|
||||
},
|
||||
profile: {
|
||||
description: 'What the profile would look like if the match is correct',
|
||||
type: 'object',
|
||||
properties: {
|
||||
display: {
|
||||
description: 'Profile name to be displayed',
|
||||
type: 'string'
|
||||
},
|
||||
uri: {
|
||||
description: 'URI or URL for public access to the profile',
|
||||
type: 'string'
|
||||
},
|
||||
qr: {
|
||||
description: 'URI or URL associated with the profile usually served as a QR code',
|
||||
type: ['string', 'null']
|
||||
}
|
||||
}
|
||||
},
|
||||
claim: {
|
||||
description: 'Details from the claim matching process',
|
||||
type: 'object',
|
||||
properties: {
|
||||
uriRegularExpression: {
|
||||
description: 'Regular expression used to parse the URI',
|
||||
type: 'string'
|
||||
},
|
||||
uriIsAmbiguous: {
|
||||
description: 'Whether this match automatically excludes other matches',
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
},
|
||||
proof: {
|
||||
description: 'Information for the proof verification process',
|
||||
type: 'object',
|
||||
properties: {
|
||||
request: {
|
||||
description: 'Details to request the potential proof',
|
||||
type: 'object',
|
||||
properties: {
|
||||
uri: {
|
||||
description: 'Location of the proof',
|
||||
type: ['string', 'null']
|
||||
},
|
||||
accessRestriction: {
|
||||
description: 'Type of access restriction [none, nocors, granted, server]',
|
||||
type: 'string'
|
||||
},
|
||||
fetcher: {
|
||||
description: 'Name of the fetcher to use',
|
||||
type: 'string'
|
||||
},
|
||||
data: {
|
||||
description: 'Data needed by the fetcher or proxy to request the proof',
|
||||
type: 'object',
|
||||
additionalProperties: true
|
||||
}
|
||||
}
|
||||
},
|
||||
response: {
|
||||
description: 'Details about the expected response',
|
||||
type: 'object',
|
||||
properties: {
|
||||
format: {
|
||||
description: 'Expected format of the proof [text, json]',
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
},
|
||||
target: {
|
||||
description: 'Details about the target located in the response',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
format: {
|
||||
description: 'How is the proof formatted [uri, fingerprint]',
|
||||
type: 'string'
|
||||
},
|
||||
encoding: {
|
||||
description: 'How is the proof encoded [plain, html, xml]',
|
||||
type: 'string'
|
||||
},
|
||||
relation: {
|
||||
description: 'How are the response and the target related [contains, equals]',
|
||||
type: 'string'
|
||||
},
|
||||
path: {
|
||||
description: 'Path to the target location if the response is JSON',
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
required: [
|
||||
'about',
|
||||
'profile',
|
||||
'claim',
|
||||
'proof'
|
||||
],
|
||||
additionalProperties: false
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* A service provider matched to an identity claim
|
||||
* @class
|
||||
* @public
|
||||
*/
|
||||
export class ServiceProvider {
|
||||
/**
|
||||
* @param {import('./types').ServiceProviderObject} serviceProviderObject - JSON representation of a {@link ServiceProvider}
|
||||
*/
|
||||
constructor (serviceProviderObject) {
|
||||
/**
|
||||
* Details about the service provider
|
||||
* @type {import('./types').ServiceProviderAbout}
|
||||
*/
|
||||
this.about = serviceProviderObject.about
|
||||
/**
|
||||
* What the profile would look like if a claim matches this service provider
|
||||
* @type {import('./types').ServiceProviderProfile}
|
||||
*/
|
||||
this.profile = serviceProviderObject.profile
|
||||
/**
|
||||
* Information about the claim matching process
|
||||
* @type {import('./types').ServiceProviderClaim}
|
||||
*/
|
||||
this.claim = serviceProviderObject.claim
|
||||
/**
|
||||
* Information for the proof verification process
|
||||
* @type {import('./types').ServiceProviderProof}
|
||||
*/
|
||||
this.proof = serviceProviderObject.proof
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON representation of the {@link ServiceProvider}
|
||||
* @function
|
||||
* @returns {import('./types').ServiceProviderObject} JSON representation of a {@link ServiceProvider}
|
||||
*/
|
||||
toJSON () {
|
||||
return {
|
||||
about: this.about,
|
||||
profile: this.profile,
|
||||
claim: this.claim,
|
||||
proof: this.proof
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,270 +0,0 @@
|
|||
/*
|
||||
Copyright 2022 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* ActivityPub service provider ({@link https://docs.keyoxide.org/service-providers/activitypub/|Keyoxide docs})
|
||||
* @module serviceProviders/activitypub
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.activitypub.processURI('https://domain.example/@alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { fetcher } from '../index.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'activitypub',
|
||||
name: 'ActivityPub',
|
||||
homepage: 'https://activitypub.rocks'
|
||||
},
|
||||
profile: {
|
||||
display: uri,
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString().toString(),
|
||||
uriIsAmbiguous: true
|
||||
},
|
||||
proof: {
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.ACTIVITYPUB,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
data: {
|
||||
url: uri
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['summary']
|
||||
},
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['attachment', 'value']
|
||||
},
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['content']
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const functions = {
|
||||
postprocess: async (/** @type {ServiceProvider} */ claimData, proofData, opts) => {
|
||||
switch (proofData.result.type) {
|
||||
case 'Note': {
|
||||
claimData.profile.uri = proofData.result.attributedTo
|
||||
claimData.profile.display = proofData.result.attributedTo
|
||||
const personData = await fetcher.activitypub.fn({ url: proofData.result.attributedTo }, opts)
|
||||
.catch(_ => null)
|
||||
if (personData) {
|
||||
claimData.profile.display = `@${personData.preferredUsername}@${new URL(claimData.proof.request.uri).hostname}`
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
case 'Person':
|
||||
claimData.profile.display = `@${proofData.result.preferredUsername}@${new URL(claimData.proof.request.uri).hostname}`
|
||||
break
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
// Attempt to fetch and process the instance's NodeInfo data
|
||||
const nodeinfo = await _processNodeinfo(new URL(claimData.proof.request.uri).hostname)
|
||||
if (nodeinfo) {
|
||||
claimData.about.name = nodeinfo.software.name
|
||||
claimData.about.id = nodeinfo.software.name
|
||||
claimData.about.homepage = nodeinfo.software.homepage
|
||||
}
|
||||
|
||||
return { claimData, proofData }
|
||||
}
|
||||
}
|
||||
|
||||
const _processNodeinfo = async (/** @type {string} */ domain) => {
|
||||
const nodeinfoRef = await fetch(`https://${domain}/.well-known/nodeinfo`)
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error('HTTP Status was not 200')
|
||||
}
|
||||
return res.json()
|
||||
})
|
||||
.catch(_ => {
|
||||
return null
|
||||
})
|
||||
|
||||
if (!nodeinfoRef) return null
|
||||
|
||||
// NodeInfo version 2.1
|
||||
{
|
||||
const nodeinfo = nodeinfoRef.links.find(x => { return x.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1' })
|
||||
if (nodeinfo) {
|
||||
return await fetch(nodeinfo.href)
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error('HTTP Status was not 200')
|
||||
}
|
||||
return res.json()
|
||||
})
|
||||
.then(res => {
|
||||
return {
|
||||
software: {
|
||||
name: res.software.name,
|
||||
version: res.software.version,
|
||||
homepage: res.software.homepage || 'https://activitypub.rocks'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(_ => {
|
||||
return null
|
||||
})
|
||||
}
|
||||
}
|
||||
// NodeInfo version 2.0
|
||||
{
|
||||
const nodeinfo = nodeinfoRef.links.find(x => { return x.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0' })
|
||||
if (nodeinfo) {
|
||||
return await fetch(nodeinfo.href)
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error('HTTP Status was not 200')
|
||||
}
|
||||
return res.json()
|
||||
})
|
||||
.then(res => {
|
||||
return {
|
||||
software: {
|
||||
name: res.software.name,
|
||||
version: res.software.version,
|
||||
homepage: 'https://activitypub.rocks'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(_ => {
|
||||
return null
|
||||
})
|
||||
}
|
||||
}
|
||||
// NodeInfo version 1.1
|
||||
{
|
||||
const nodeinfo = nodeinfoRef.links.find(x => { return x.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.1' })
|
||||
if (nodeinfo) {
|
||||
return await fetch(nodeinfo.href)
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error('HTTP Status was not 200')
|
||||
}
|
||||
return res.json()
|
||||
})
|
||||
.then(res => {
|
||||
return {
|
||||
software: {
|
||||
name: res.software.name,
|
||||
version: res.software.version,
|
||||
homepage: 'https://activitypub.rocks'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(_ => {
|
||||
return null
|
||||
})
|
||||
}
|
||||
}
|
||||
// NodeInfo version 1.0
|
||||
{
|
||||
const nodeinfo = nodeinfoRef.links.find(x => { return x.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0' })
|
||||
if (nodeinfo) {
|
||||
return await fetch(nodeinfo.href)
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw new Error('HTTP Status was not 200')
|
||||
}
|
||||
return res.json()
|
||||
})
|
||||
.then(res => {
|
||||
return {
|
||||
software: {
|
||||
name: res.software.name,
|
||||
version: res.software.version,
|
||||
homepage: 'https://activitypub.rocks'
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(_ => {
|
||||
return null
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://domain.org',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/@/alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/@alice',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/@alice/123456',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/u/alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/users/alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/users/alice/123456',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'http://domain.org/alice',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -1,95 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* ASPE service provider ({@link https://docs.keyoxide.org/service-providers/aspe/|Keyoxide docs})
|
||||
* @module serviceProviders/aspe
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.activitypub.processURI('aspe:domain.example:abc123def456');
|
||||
*/
|
||||
|
||||
import isFQDN from 'validator/lib/isFQDN.js'
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^aspe:([a-zA-Z0-9.\-_]*):([a-zA-Z0-9]*)/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
if (!isFQDN(match[1])) {
|
||||
return null
|
||||
}
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'aspe',
|
||||
name: 'ASPE'
|
||||
},
|
||||
profile: {
|
||||
display: uri,
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
request: {
|
||||
uri: null,
|
||||
fetcher: E.Fetcher.ASPE,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
data: {
|
||||
aspeUri: uri
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['claims']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'aspe:domain.tld:abc123def456',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'aspe:domain.tld',
|
||||
shouldMatch: false
|
||||
},
|
||||
{
|
||||
uri: 'dns:domain.tld',
|
||||
shouldMatch: false
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.tld',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -1,127 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 Bram Hagens
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Discord service provider
|
||||
* @module serviceProviders/discord
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.discord.processURI('https://discord.com/invite/AbCdEf');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(?:discord\.gg|discord\.com\/invite)\/(.+)/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'discord',
|
||||
name: 'Discord',
|
||||
homepage: 'https://discord.com'
|
||||
},
|
||||
profile: {
|
||||
display: null,
|
||||
uri: null,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
// Get proof from invites (https://discord.com/developers/docs/resources/invite#get-invite)
|
||||
// See https://discord.com/developers/docs/reference#api-versioning for Discord's API versioning
|
||||
proof: {
|
||||
request: {
|
||||
uri: `https://discord.com/api/v10/invites/${match[1]}`,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
data: {
|
||||
url: `https://discord.com/api/v10/invites/${match[1]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['guild', 'description']
|
||||
},
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['guild', 'name']
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const functions = {
|
||||
postprocess: async (claimData, proofData, opts) => {
|
||||
// Extract inviter's username from https://discord.com/developers/docs/resources/invite#invite-object
|
||||
claimData.profile.display = proofData.result.inviter.username
|
||||
|
||||
return { claimData, proofData }
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://discord.com/invite/AbCdEf',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://discord.com/invite/AbCdEfGh',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://discord.gg/AbCdEf',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://discord.gg/AbCdEfGh',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.com/invite/AbCdEf',
|
||||
shouldMatch: false
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.gg/AbCdEf',
|
||||
shouldMatch: false
|
||||
},
|
||||
{
|
||||
uri: 'https://discord.com/invite/',
|
||||
shouldMatch: false
|
||||
},
|
||||
{
|
||||
uri: 'https://discord.gg/',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -1,101 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Forgejo service provider ({@link https://docs.keyoxide.org/service-providers/forgejo/|Keyoxide docs})
|
||||
* @module serviceProviders/forgejo
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.forgejo.processURI('https://domain.example/alice/repo');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { fetcher } from '../index.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'forgejo',
|
||||
name: 'Forgejo',
|
||||
homepage: 'https://forgejo.org'
|
||||
},
|
||||
profile: {
|
||||
display: `${match[2]}@${match[1]}`,
|
||||
uri: `https://${match[1]}/${match[2]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: true
|
||||
},
|
||||
proof: {
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
data: {
|
||||
url: `https://${match[1]}/api/v1/repos/${match[2]}/${match[3]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['description']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const functions = {
|
||||
validate: async (/** @type {ServiceProvider} */ claimData, proofData, opts) => {
|
||||
const url = `https://${new URL(claimData.proof.request.uri).hostname}/api/forgejo/v1/version`
|
||||
const forgejoData = await fetcher.http.fn({ url, format: E.ProofFormat.JSON }, opts)
|
||||
return forgejoData && 'version' in forgejoData
|
||||
}
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://domain.org/alice/forgejo_proof',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/alice/forgejo_proof/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/alice/other_proof',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/alice',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -1,96 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Lobste.rs service provider ({@link https://docs.keyoxide.org/service-providers/lobsters/|Keyoxide docs})
|
||||
* @module serviceProviders/lobsters
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.lobsters.processURI('https://lobste.rs/~alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/lobste\.rs\/(?:~|u\/)(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'lobsters',
|
||||
name: 'Lobsters',
|
||||
homepage: 'https://lobste.rs'
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri: `https://lobste.rs/~${match[1]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
request: {
|
||||
uri: `https://lobste.rs/~${match[1]}.json`,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
data: {
|
||||
url: `https://lobste.rs/~${match[1]}.json`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['about']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://lobste.rs/~Alice',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://lobste.rs/u/Alice',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://lobste.rs/u/Alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/~Alice',
|
||||
shouldMatch: false
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/u/Alice',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -1,179 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* OpenPGP service provider ({@link https://docs.keyoxide.org/service-providers/openpgp/|Keyoxide docs})
|
||||
* @module serviceProviders/openpgp
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.openpgp.processURI('openpgp4fpr:ABC123DEF456');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^(.*)/
|
||||
|
||||
const reURIHkp = /^openpgp4fpr:(?:0x)?([a-zA-Z0-9.\-_]*)/
|
||||
const reURIWkdDirect = /^https:\/\/(.*)\/.well-known\/openpgpkey\/hu\/([a-zA-Z0-9]*)(?:\?l=(.*))?/
|
||||
const reURIWkdAdvanced = /^https:\/\/(openpgpkey.*)\/.well-known\/openpgpkey\/(.*)\/hu\/([a-zA-Z0-9]*)(?:\?l=(.*))?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
let reURI = null
|
||||
let mode = null
|
||||
let match = null
|
||||
|
||||
if (reURIHkp.test(uri)) {
|
||||
reURI = reURIHkp
|
||||
mode = E.OpenPgpQueryProtocol.HKP
|
||||
match = uri.match(reURI)
|
||||
}
|
||||
if (!mode && reURIWkdAdvanced.test(uri)) {
|
||||
reURI = reURIWkdAdvanced
|
||||
mode = E.OpenPgpQueryProtocol.WKD
|
||||
match = uri.match(reURI)
|
||||
}
|
||||
if (!mode && reURIWkdDirect.test(uri)) {
|
||||
reURI = reURIWkdDirect
|
||||
mode = E.OpenPgpQueryProtocol.WKD
|
||||
match = uri.match(reURI)
|
||||
}
|
||||
|
||||
let output = null
|
||||
|
||||
switch (mode) {
|
||||
case E.OpenPgpQueryProtocol.HKP:
|
||||
output = new ServiceProvider({
|
||||
about: {
|
||||
id: 'openpgp',
|
||||
name: 'OpenPGP'
|
||||
},
|
||||
profile: {
|
||||
display: `openpgp4fpr:${match[1]}`,
|
||||
uri: `https://keys.openpgp.org/search?q=${match[1]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
request: {
|
||||
uri: `https://keys.openpgp.org/vks/v1/by-fingerprint/${match[1].toUpperCase()}`,
|
||||
fetcher: E.Fetcher.OPENPGP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
data: {
|
||||
url: `https://keys.openpgp.org/vks/v1/by-fingerprint/${match[1].toUpperCase()}`,
|
||||
protocol: E.OpenPgpQueryProtocol.HKP
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['notations', 'proof@ariadne.id']
|
||||
}]
|
||||
}
|
||||
})
|
||||
break
|
||||
case E.OpenPgpQueryProtocol.WKD:
|
||||
output = new ServiceProvider({
|
||||
about: {
|
||||
id: 'openpgp',
|
||||
name: 'OpenPGP'
|
||||
},
|
||||
profile: {
|
||||
display: 'unknown fingerprint',
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.OPENPGP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
data: {
|
||||
url: uri,
|
||||
protocol: E.OpenPgpQueryProtocol.WKD
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['notations', 'proof@ariadne.id']
|
||||
}]
|
||||
}
|
||||
})
|
||||
break
|
||||
}
|
||||
|
||||
return output
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'openpgp4fpr:123456789',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'openpgp4fpr:abcdef123',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://openpgpkey.domain.tld/.well-known/openpgpkey/domain.tld/hu/123abc456def?l=name',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://openpgpkey.domain.tld/.well-known/openpgpkey/domain.tld/hu/123abc456def',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.tld/.well-known/openpgpkey/hu/123abc456def?l=name',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.tld/.well-known/openpgpkey/hu/123abc456def',
|
||||
shouldMatch: true
|
||||
},
|
||||
// The following will not pass .processURI, but reURI currently accepts anything
|
||||
{
|
||||
uri: 'https://domain.tld',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://openpgpkey.domain.tld/.well-known/openpgpkey/hu/123abc456def?l=name',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.tld/.well-known/openpgpkey/123abc456def?l=name',
|
||||
shouldMatch: true
|
||||
}
|
||||
]
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
Copyright 2023 Tim Haase
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* ORCiD service provider ({@link https://docs.keyoxide.org/service-providers/orcid/|Keyoxide docs})
|
||||
* @module serviceProviders/orcid
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.orcid.processURI('https://orcid.org/123-456-789-123');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/orcid\.org\/(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'orcid',
|
||||
name: 'ORCiD',
|
||||
homepage: 'https://orcid.org/'
|
||||
},
|
||||
profile: {
|
||||
display: match[1],
|
||||
uri,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||
data: {
|
||||
url: uri,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['person', 'biography', 'content']
|
||||
}, {
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['person', 'researcher-urls', 'researcher-url', 'url', 'value']
|
||||
}, {
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.EQUALS,
|
||||
path: ['person', 'keywords', 'keyword', 'content']
|
||||
}]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://orcid.org/0000-0000-0000-0000',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://orcid.org/0000-0000-0000-0000/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://domain.org/0000-0000-0000-0000',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -1,100 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 Tyler Beckman
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* pronouns.cc service provider
|
||||
* @module serviceProviders/pronounscc
|
||||
* @example
|
||||
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||
* const sp = ServiceProviderDefinitions.data.pronounscc.processURI('https://pronouns.cc/@Alice');
|
||||
*/
|
||||
|
||||
import * as E from '../enums.js'
|
||||
import { ServiceProvider } from '../serviceProvider.js'
|
||||
|
||||
export const reURI = /^https:\/\/pronouns\.cc\/@(.*)\/?/
|
||||
|
||||
/**
|
||||
* @function
|
||||
* @param {string} uri - Claim URI to process
|
||||
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||
*/
|
||||
export function processURI (uri) {
|
||||
const match = uri.match(reURI)
|
||||
|
||||
return new ServiceProvider({
|
||||
about: {
|
||||
id: 'pronounscc',
|
||||
name: 'pronouns.cc',
|
||||
homepage: 'https://pronouns.cc'
|
||||
},
|
||||
profile: {
|
||||
display: `@${match[1]}`,
|
||||
uri: `https://pronouns.cc/@${match[1]}`,
|
||||
qr: null
|
||||
},
|
||||
claim: {
|
||||
uriRegularExpression: reURI.toString(),
|
||||
uriIsAmbiguous: false
|
||||
},
|
||||
proof: {
|
||||
request: {
|
||||
uri,
|
||||
fetcher: E.Fetcher.HTTP,
|
||||
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||
data: {
|
||||
url: `https://pronouns.cc/api/v1/users/${match[1]}`,
|
||||
format: E.ProofFormat.JSON
|
||||
}
|
||||
},
|
||||
response: {
|
||||
format: E.ProofFormat.JSON
|
||||
},
|
||||
target: [
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['links']
|
||||
},
|
||||
{
|
||||
format: E.ClaimFormat.URI,
|
||||
encoding: E.EntityEncodingFormat.PLAIN,
|
||||
relation: E.ClaimRelation.CONTAINS,
|
||||
path: ['bio']
|
||||
}
|
||||
]
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const tests = [
|
||||
{
|
||||
uri: 'https://pronouns.cc/@Alice',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://pronouns.cc/@Alice/',
|
||||
shouldMatch: true
|
||||
},
|
||||
{
|
||||
uri: 'https://pronouns.cc/Alice',
|
||||
shouldMatch: false
|
||||
},
|
||||
{
|
||||
uri: 'https://pronouns.cc/Alice/',
|
||||
shouldMatch: false
|
||||
}
|
||||
]
|
|
@ -13,25 +13,38 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { CleartextMessage, PublicKey, readCleartextMessage, verify } from 'openpgp'
|
||||
import { readCleartextMessage, verify } from 'openpgp'
|
||||
import { Claim } from './claim.js'
|
||||
import { fetchURI } from './openpgp.js'
|
||||
import { Profile } from './profile.js'
|
||||
import { ProfileType, PublicKeyEncoding, PublicKeyType } from './enums.js'
|
||||
import { Persona } from './persona.js'
|
||||
import { fetchURI } from './keys.js'
|
||||
|
||||
/**
|
||||
* @module signatures
|
||||
*/
|
||||
|
||||
/**
|
||||
* Extract the profile from a signature and fetch the associated key
|
||||
* @param {string} signature - The plaintext signature to parse
|
||||
* @returns {Promise<Profile>} The profile obtained from the signature
|
||||
* Extract data from a signature and fetch the associated key
|
||||
* @async
|
||||
* @param {string} signature - The plaintext signature to process
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function parse (signature) {
|
||||
/** @type {CleartextMessage} */
|
||||
export async function process (signature) {
|
||||
/** @type {import('openpgp').CleartextMessage} */
|
||||
let sigData
|
||||
const result = {
|
||||
fingerprint: null,
|
||||
users: [
|
||||
{
|
||||
userData: {},
|
||||
claims: []
|
||||
}
|
||||
],
|
||||
primaryUserIndex: null,
|
||||
key: {
|
||||
data: null,
|
||||
fetchMethod: null,
|
||||
uri: null
|
||||
}
|
||||
}
|
||||
|
||||
// Read the signature
|
||||
try {
|
||||
|
@ -52,7 +65,6 @@ export async function parse (signature) {
|
|||
'https://keys.openpgp.org/'
|
||||
const text = sigData.getText()
|
||||
const sigKeys = []
|
||||
const claims = []
|
||||
|
||||
text.split('\n').forEach((line, i) => {
|
||||
const match = line.match(/^([a-zA-Z0-9]*)=(.*)$/i)
|
||||
|
@ -65,7 +77,7 @@ export async function parse (signature) {
|
|||
break
|
||||
|
||||
case 'proof':
|
||||
claims.push(new Claim(match[2]))
|
||||
result.users[0].claims.push(new Claim(match[2]))
|
||||
break
|
||||
|
||||
default:
|
||||
|
@ -73,49 +85,39 @@ export async function parse (signature) {
|
|||
}
|
||||
})
|
||||
|
||||
const obtainedKey = {
|
||||
query: null,
|
||||
data: null,
|
||||
method: null
|
||||
}
|
||||
|
||||
// Try key identifier found in the signature
|
||||
// Try overruling key
|
||||
if (sigKeys.length > 0) {
|
||||
try {
|
||||
obtainedKey.query = sigKeys[0]
|
||||
/** @type {PublicKey} */
|
||||
obtainedKey.data = (await fetchURI(obtainedKey.query)).publicKey.key
|
||||
obtainedKey.method = obtainedKey.query.split(':')[0]
|
||||
result.key.uri = sigKeys[0]
|
||||
result.key.data = await fetchURI(result.key.uri)
|
||||
result.key.fetchMethod = result.key.uri.split(':')[0]
|
||||
} catch (e) {}
|
||||
}
|
||||
// Try WKD
|
||||
if (!obtainedKey.data && signersUserID) {
|
||||
if (!result.key.data && signersUserID) {
|
||||
try {
|
||||
obtainedKey.query = signersUserID
|
||||
obtainedKey.data = (await fetchURI(`wkd:${signersUserID}`)).publicKey.key
|
||||
obtainedKey.method = 'wkd'
|
||||
result.key.uri = `wkd:${signersUserID}`
|
||||
result.key.data = await fetchURI(result.key.uri)
|
||||
result.key.fetchMethod = 'wkd'
|
||||
} catch (e) {}
|
||||
}
|
||||
// Try HKP
|
||||
if (!obtainedKey.data) {
|
||||
if (!result.key.data) {
|
||||
try {
|
||||
const match = preferredKeyServer.match(/^(.*:\/\/)?([^/]*)(?:\/)?$/i)
|
||||
obtainedKey.query = issuerKeyID || signersUserID
|
||||
obtainedKey.data = (await fetchURI(`hkp:${match[2]}:${obtainedKey.query}`)).publicKey.key
|
||||
obtainedKey.method = 'hkp'
|
||||
result.key.uri = `hkp:${match[2]}:${issuerKeyID || signersUserID}`
|
||||
result.key.data = await fetchURI(result.key.uri)
|
||||
result.key.fetchMethod = 'hkp'
|
||||
} catch (e) {
|
||||
throw new Error('Public key not found')
|
||||
}
|
||||
}
|
||||
|
||||
const primaryUserData = await obtainedKey.data.getPrimaryUser()
|
||||
const fingerprint = obtainedKey.data.getFingerprint()
|
||||
|
||||
// Verify the signature
|
||||
const verificationResult = await verify({
|
||||
// @ts-ignore
|
||||
message: sigData,
|
||||
verificationKeys: obtainedKey.data
|
||||
verificationKeys: result.key.data
|
||||
})
|
||||
const { verified } = verificationResult.signatures[0]
|
||||
try {
|
||||
|
@ -124,25 +126,35 @@ export async function parse (signature) {
|
|||
throw new Error(`Signature could not be verified (${e.message})`)
|
||||
}
|
||||
|
||||
// Build the persona
|
||||
const persona = new Persona(primaryUserData.user.userID.name, [])
|
||||
persona.setIdentifier(primaryUserData.user.userID.userID)
|
||||
persona.setDescription(primaryUserData.user.userID.comment || null)
|
||||
persona.setEmailAddress(primaryUserData.user.userID.email || null)
|
||||
persona.claims = claims
|
||||
.map(
|
||||
({ value }) =>
|
||||
new Claim(new TextDecoder().decode(value), `openpgp4fpr:${fingerprint}`)
|
||||
)
|
||||
result.fingerprint = result.key.data.keyPacket.getFingerprint()
|
||||
|
||||
const profile = new Profile(ProfileType.OPENPGP, `openpgp4fpr:${fingerprint}`, [persona])
|
||||
result.users[0].claims.forEach((claim) => {
|
||||
claim.fingerprint = result.fingerprint
|
||||
})
|
||||
|
||||
profile.publicKey.keyType = PublicKeyType.OPENPGP
|
||||
profile.publicKey.encoding = PublicKeyEncoding.ARMORED_PGP
|
||||
profile.publicKey.encodedKey = obtainedKey.data.armor()
|
||||
profile.publicKey.key = obtainedKey.data
|
||||
profile.publicKey.fetch.method = obtainedKey.method
|
||||
profile.publicKey.fetch.query = obtainedKey.query
|
||||
const primaryUserData = await result.key.data.getPrimaryUser()
|
||||
let userData
|
||||
|
||||
return profile
|
||||
if (signersUserID) {
|
||||
result.key.data.users.forEach((/** @type {{ userID: { email: string; }; }} */ user) => {
|
||||
if (user.userID.email === signersUserID) {
|
||||
userData = user
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!userData) {
|
||||
userData = primaryUserData.user
|
||||
}
|
||||
|
||||
result.users[0].userData = {
|
||||
id: userData.userID ? userData.userID.userID : null,
|
||||
name: userData.userID ? userData.userID.name : null,
|
||||
email: userData.userID ? userData.userID.email : null,
|
||||
comment: userData.userID ? userData.userID.comment : null,
|
||||
isPrimary: primaryUserData.user.userID.userID === userData.userID.userID
|
||||
}
|
||||
|
||||
result.primaryUserIndex = result.users[0].userData.isPrimary ? 0 : null
|
||||
|
||||
return result
|
||||
}
|
||||
|
|
198
src/types.js
198
src/types.js
|
@ -1,198 +0,0 @@
|
|||
/*
|
||||
Copyright 2024 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Contains various types
|
||||
* @module types
|
||||
*/
|
||||
|
||||
import { PublicKeyType, PublicKeyEncoding, PublicKeyFetchMethod, ProxyPolicy, ClaimFormat, EntityEncodingFormat, ClaimRelation, ProofAccessRestriction, ProofFormat } from './enums'
|
||||
|
||||
/**
|
||||
* Service provider
|
||||
* @typedef {object} ServiceProviderObject
|
||||
* @property {ServiceProviderAbout} about - Details about the service provider
|
||||
* @property {ServiceProviderProfile} profile - What the profile would look like if a claim matches this service provider
|
||||
* @property {ServiceProviderClaim} claim - Details from the claim matching process
|
||||
* @property {ServiceProviderProof} proof - Information for the proof verification process
|
||||
*/
|
||||
|
||||
/**
|
||||
* Details about the service provider
|
||||
* @typedef {object} ServiceProviderAbout
|
||||
* @property {string} id - Identifier of the service provider (no whitespace or symbols, lowercase)
|
||||
* @property {string} name - Full name of the service provider
|
||||
* @property {string} [homepage] - URL to the homepage of the service provider
|
||||
*/
|
||||
|
||||
/**
|
||||
* What the profile would look like if a claim matches this service provider
|
||||
* @typedef {object} ServiceProviderProfile
|
||||
* @property {string} display - Profile name to be displayed
|
||||
* @property {string} uri - URI or URL for public access to the profile
|
||||
* @property {string} [qr] -URI or URL associated with the profile usually served as a QR code
|
||||
*/
|
||||
|
||||
/**
|
||||
* Information about the claim matching process
|
||||
* @typedef {object} ServiceProviderClaim
|
||||
* @property {string} uriRegularExpression - Regular expression used to parse the URI
|
||||
* @property {boolean} uriIsAmbiguous - Whether this match automatically excludes other matches
|
||||
*/
|
||||
|
||||
/**
|
||||
* Information for the proof verification process
|
||||
* @typedef {object} ServiceProviderProof
|
||||
* @property {ServiceProviderProofRequest} request - Details to request the potential proof
|
||||
* @property {ServiceProviderProofResponse} response - Details about the expected response
|
||||
* @property {Array<ProofTarget>} target - Details about the target located in the response
|
||||
*/
|
||||
|
||||
/**
|
||||
* Details to request the potential proof
|
||||
* @typedef {object} ServiceProviderProofRequest
|
||||
* @property {string} [uri] - Location of the proof
|
||||
* @property {string} fetcher - Fetcher to be used to request the proof
|
||||
* @property {ProofAccessRestriction} accessRestriction - Type of access restriction
|
||||
* @property {object} data - Data needed by the fetcher or proxy to request the proof
|
||||
*/
|
||||
|
||||
/**
|
||||
* Details about the expected response
|
||||
* @typedef {object} ServiceProviderProofResponse
|
||||
* @property {ProofFormat} format - Expected format of the proof
|
||||
*/
|
||||
|
||||
/**
|
||||
* Public key for a profile
|
||||
* @typedef {object} ProfilePublicKey
|
||||
* @property {PublicKeyType} keyType - The type of cryptographic key
|
||||
* @property {PublicKeyEncoding} encoding - The encoding of the cryptographic key
|
||||
* @property {string} [fingerprint] - The fingerprint of the cryptographic key
|
||||
* @property {string} [encodedKey] - The encoded cryptographic key
|
||||
* @property {import('openpgp').PublicKey | import('jose').JWK} [key] - The raw cryptographic key as object (to be removed during toJSON())
|
||||
* @property {ProfilePublicKeyFetch} fetch - Details on how to fetch the public key
|
||||
*/
|
||||
|
||||
/**
|
||||
* Details on how to fetch the public key
|
||||
* @typedef {object} ProfilePublicKeyFetch
|
||||
* @property {PublicKeyFetchMethod} method - The method to fetch the key
|
||||
* @property {string} [query] - The query to fetch the key
|
||||
* @property {string} [resolvedUrl] - The URL the method eventually resolved to
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config used for the claim verification
|
||||
* @typedef {object} VerificationConfig
|
||||
* @property {ProxyVerificationConfig} [proxy] - Options related to the use of proxy servers
|
||||
* @property {ClaimVerificationConfig} [claims] - Config related to the verification of supported claims
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config related to the use of proxy servers
|
||||
* @typedef {object} ProxyVerificationConfig
|
||||
* @property {string} [scheme] - The scheme to use for proxy requests
|
||||
* @property {string} [hostname] - The hostname of the proxy
|
||||
* @property {ProxyPolicy} policy - The policy that defines when to use a proxy
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config related to the verification of supported claims
|
||||
* @typedef {object} ClaimVerificationConfig
|
||||
* @property {ActivityPubClaimVerificationConfig} [activitypub] - Config related to the verification of ActivityPub claims
|
||||
* @property {IrcClaimVerificationConfig} [irc] - Config related to the verification of IRC claims
|
||||
* @property {MatrixClaimVerificationConfig} [matrix] - Config related to the verification of Matrix claims
|
||||
* @property {TelegramClaimVerificationConfig} [telegram] - Config related to the verification of Telegram claims
|
||||
* @property {XmppClaimVerificationConfig} [xmpp] - Config related to the verification of XMPP claims
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config related to the verification of ActivityPub claims
|
||||
* @typedef {object} ActivityPubClaimVerificationConfig
|
||||
* @property {string} url - The URL of the verifier account
|
||||
* @property {string} privateKey - The private key to sign the request
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config related to the verification of IRC claims
|
||||
* @typedef {object} IrcClaimVerificationConfig
|
||||
* @property {string} nick - The nick that the library uses to connect to the IRC server
|
||||
* @property {{ domainRegex: string; username: string; password: string; }[]} sasl - An array of possible SASL logins
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config related to the verification of Matrix claims
|
||||
* @typedef {object} MatrixClaimVerificationConfig
|
||||
* @property {string} instance - The server hostname on which the library can log in
|
||||
* @property {string} accessToken - The access token required to identify the library ({@link https://www.matrix.org/docs/guides/client-server-api|Matrix docs})
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config related to the verification of Telegram claims
|
||||
* @typedef {object} TelegramClaimVerificationConfig
|
||||
* @property {string} token - The Telegram API's token ({@link https://core.telegram.org/bots/api#authorizing-your-bot|Telegram docs})
|
||||
*/
|
||||
|
||||
/**
|
||||
* Config related to the verification of XMPP claims
|
||||
* @typedef {object} XmppClaimVerificationConfig
|
||||
* @property {string} service - The server hostname on which the library can log in
|
||||
* @property {string} username - The username used to log in
|
||||
* @property {string} password - The password used to log in
|
||||
*/
|
||||
|
||||
/**
|
||||
* The online verifier instance of identity profiles like Keyoxide's web interface
|
||||
* @typedef {object} ProfileVerifier
|
||||
* @property {string} name - Name of the profile verifier
|
||||
* @property {string} url - URL to the profile verifier
|
||||
*/
|
||||
|
||||
/**
|
||||
* Parameters needed to perform the proof verification
|
||||
* @typedef {object} VerificationParams
|
||||
* @property {string} target - Proof to search
|
||||
* @property {ClaimFormat} claimFormat - Format of the claim
|
||||
* @property {EntityEncodingFormat} proofEncodingFormat - Encoding of the data containing the proof
|
||||
* @property {ClaimRelation} [claimRelation] - How to find the proof inside the JSON data
|
||||
*/
|
||||
|
||||
/**
|
||||
* Result of the proof verification
|
||||
* @typedef {object} VerificationResult
|
||||
* @property {boolean} result - Whether the proof was found and the claim verified
|
||||
* @property {boolean} completed - Whether the verification process completed without errors
|
||||
* @property {VerificationResultProof} [proof] - Details about the proof and how it was fetched
|
||||
* @property {Array<any>} errors - Errors that ocurred during the verification process
|
||||
*/
|
||||
|
||||
/**
|
||||
* Information about the proof in the proof verification result
|
||||
* @typedef {object} VerificationResultProof
|
||||
* @property {string} fetcher - Which fetcher was used to obtain the data containing the proof
|
||||
* @property {boolean} viaProxy - Whether a proxy was used to obtain the data containing the proof
|
||||
*/
|
||||
|
||||
/**
|
||||
* The method to find the proof inside the response data
|
||||
* @typedef {object} ProofTarget
|
||||
* @property {ClaimFormat} format - How the response data is formatted
|
||||
* @property {EntityEncodingFormat} encoding - How the response data is encoded
|
||||
* @property {ClaimRelation} relation - How the proof is related to the response data
|
||||
* @property {Array<string>} path - Path to the proof inside the response data object
|
||||
*/
|
||||
|
||||
export const Types = {}
|
25
src/utils.js
25
src/utils.js
|
@ -22,10 +22,13 @@ import { ClaimFormat } from './enums.js'
|
|||
|
||||
/**
|
||||
* Generate an URL to request data from a proxy server
|
||||
* @param {string} type - The name of the fetcher the proxy must use
|
||||
* @param {object} data - The data the proxy must provide to the fetcher
|
||||
* @param {import('./types').VerificationConfig} opts - Options to enable the request
|
||||
* @returns {string} Generated proxy URL
|
||||
* @param {string} type - The name of the fetcher the proxy must use
|
||||
* @param {object} data - The data the proxy must provide to the fetcher
|
||||
* @param {object} opts - Options to enable the request
|
||||
* @param {object} opts.proxy - Proxy related options
|
||||
* @param {object} opts.proxy.scheme - The scheme used by the proxy server
|
||||
* @param {object} opts.proxy.hostname - The hostname of the proxy server
|
||||
* @returns {string}
|
||||
*/
|
||||
export function generateProxyURL (type, data, opts) {
|
||||
try {
|
||||
|
@ -40,18 +43,18 @@ export function generateProxyURL (type, data, opts) {
|
|||
queryStrings.push(`${key}=${encodeURIComponent(data[key])}`)
|
||||
})
|
||||
|
||||
const scheme = opts.proxy.scheme ?? 'https'
|
||||
const scheme = opts.proxy.scheme ? opts.proxy.scheme : 'https'
|
||||
|
||||
return `${scheme}://${opts.proxy.hostname}/api/3/get/${type}?${queryStrings.join(
|
||||
return `${scheme}://${opts.proxy.hostname}/api/2/get/${type}?${queryStrings.join(
|
||||
'&'
|
||||
)}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the string that must be found in the proof to verify a claim
|
||||
* @param {string} fingerprint - The fingerprint of the claim
|
||||
* @param {ClaimFormat} format - The claim's format
|
||||
* @returns {string} Generate claim
|
||||
* @param {string} fingerprint - The fingerprint of the claim
|
||||
* @param {string} format - The claim's format (see {@link module:enums~ClaimFormat|enums.ClaimFormat})
|
||||
* @returns {string}
|
||||
*/
|
||||
export function generateClaim (fingerprint, format) {
|
||||
switch (format) {
|
||||
|
@ -69,8 +72,8 @@ export function generateClaim (fingerprint, format) {
|
|||
|
||||
/**
|
||||
* Get the URIs from a string and return them as an array
|
||||
* @param {string} text - The text that may contain URIs
|
||||
* @returns {Array<string>} List of URIs extracted from input
|
||||
* @param {string} text - The text that may contain URIs
|
||||
* @returns {Array<string>}
|
||||
*/
|
||||
export function getUriFromString (text) {
|
||||
const re = /((([A-Za-z0-9]+:(?:\/\/)?)(?:[-;:&=+$,\w]+@)?[A-Za-z0-9.-]+|(?:www\.|[-;:&=+$,\w]+@)[A-Za-z0-9.-]+)((?:\/[+~%/.\w\-_]*)?\??(?:[-+=&;%@.\w_]*)#?(?:[.!/\\\w]*))?)/gi
|
||||
|
|
|
@ -17,7 +17,6 @@ import { generateClaim, getUriFromString } from './utils.js'
|
|||
import { ClaimFormat, EntityEncodingFormat, ClaimRelation, ProofFormat } from './enums.js'
|
||||
import { bcryptVerify, argon2Verify } from 'hash-wasm'
|
||||
import { decodeHTML, decodeXML } from 'entities'
|
||||
import { ServiceProvider } from './serviceProvider.js'
|
||||
|
||||
/**
|
||||
* @module verifications
|
||||
|
@ -25,11 +24,14 @@ import { ServiceProvider } from './serviceProvider.js'
|
|||
*/
|
||||
|
||||
/**
|
||||
* Check if string contains the proof
|
||||
* @function
|
||||
* @param {string} data - Data potentially containing the proof
|
||||
* @param {import('./types').VerificationParams} params - Verification parameters
|
||||
* @returns {Promise<boolean>} Whether the proof was found in the string
|
||||
* @param {string} data
|
||||
* @param {object} params
|
||||
* @param {string} params.target
|
||||
* @param {string} params.claimFormat
|
||||
* @param {string} params.proofEncodingFormat
|
||||
* @param {string} [params.claimRelation]
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const containsProof = async (data, params) => {
|
||||
const fingerprintFormatted = generateClaim(params.target, params.claimFormat)
|
||||
|
@ -87,7 +89,7 @@ const containsProof = async (data, params) => {
|
|||
if (parseInt(match[0].split('$')[2]) > 12) continue
|
||||
|
||||
const hashPromise = bcryptVerify({
|
||||
password: fingerprintURI.toLowerCase(),
|
||||
password: fingerprintURI,
|
||||
hash: match[0]
|
||||
})
|
||||
.then(result => result)
|
||||
|
@ -100,28 +102,6 @@ const containsProof = async (data, params) => {
|
|||
} catch (err) {
|
||||
result = false
|
||||
}
|
||||
|
||||
// Accept mixed-case fingerprints until deadline
|
||||
if (!result) {
|
||||
try {
|
||||
// Patch until promise.race properly works on WASM
|
||||
if (parseInt(match[0].split('$')[2]) > 12) continue
|
||||
|
||||
const hashPromise = bcryptVerify({
|
||||
password: fingerprintURI,
|
||||
hash: match[0]
|
||||
})
|
||||
.then(result => result)
|
||||
.catch(_ => false)
|
||||
|
||||
result = await Promise.race([hashPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
} catch (err) {
|
||||
result = false
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
case 'argon2':
|
||||
|
@ -130,7 +110,7 @@ const containsProof = async (data, params) => {
|
|||
case 'argon2id':
|
||||
try {
|
||||
const hashPromise = argon2Verify({
|
||||
password: fingerprintURI.toLowerCase(),
|
||||
password: fingerprintURI,
|
||||
hash: match[0]
|
||||
})
|
||||
.then(result => result)
|
||||
|
@ -143,25 +123,6 @@ const containsProof = async (data, params) => {
|
|||
} catch (err) {
|
||||
result = false
|
||||
}
|
||||
|
||||
// Accept mixed-case fingerprints until deadline
|
||||
if (!result) {
|
||||
try {
|
||||
const hashPromise = argon2Verify({
|
||||
password: fingerprintURI,
|
||||
hash: match[0]
|
||||
})
|
||||
.then(result => result)
|
||||
.catch(_ => false)
|
||||
|
||||
result = await Promise.race([hashPromise, timeoutPromise]).then((result) => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
} catch (err) {
|
||||
result = false
|
||||
}
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
|
@ -213,12 +174,15 @@ const containsProof = async (data, params) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Run a JSON object through the verification process
|
||||
* @function
|
||||
* @param {*} proofData - Data potentially containing the proof
|
||||
* @param {Array<string>} checkPath - Paths to check for proof
|
||||
* @param {import('./types').VerificationParams} params - Verification parameters
|
||||
* @returns {Promise<boolean>} Whether the proof was found in the object
|
||||
* @param {any} proofData
|
||||
* @param {string} checkPath
|
||||
* @param {object} params
|
||||
* @param {string} params.target
|
||||
* @param {string} params.claimFormat
|
||||
* @param {string} params.proofEncodingFormat
|
||||
* @param {string} [params.claimRelation]
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
const runJSON = async (proofData, checkPath, params) => {
|
||||
if (!proofData) {
|
||||
|
@ -265,24 +229,24 @@ const runJSON = async (proofData, checkPath, params) => {
|
|||
}
|
||||
|
||||
/**
|
||||
* Run the verification by searching for the proof in the fetched data
|
||||
* @param {object} proofData - The proof data
|
||||
* @param {ServiceProvider} claimData - The claim data
|
||||
* @param {string} fingerprint - The fingerprint
|
||||
* @returns {Promise<import('./types').VerificationResult>} Result of the verification
|
||||
* Run the verification by finding the formatted fingerprint in the proof
|
||||
* @async
|
||||
* @param {object} proofData - The proof data
|
||||
* @param {object} claimData - The claim data
|
||||
* @param {string} fingerprint - The fingerprint
|
||||
* @returns {Promise<object>}
|
||||
*/
|
||||
export async function run (proofData, claimData, fingerprint) {
|
||||
/** @type {import('./types').VerificationResult} */
|
||||
const res = {
|
||||
result: false,
|
||||
completed: false,
|
||||
errors: []
|
||||
}
|
||||
|
||||
switch (claimData.proof.response.format) {
|
||||
switch (claimData.proof.request.format) {
|
||||
case ProofFormat.JSON:
|
||||
for (let index = 0; index < claimData.proof.target.length; index++) {
|
||||
const claimMethod = claimData.proof.target[index]
|
||||
for (let index = 0; index < claimData.claim.length; index++) {
|
||||
const claimMethod = claimData.claim[index]
|
||||
try {
|
||||
res.result = res.result || await runJSON(
|
||||
proofData,
|
||||
|
@ -301,8 +265,8 @@ export async function run (proofData, claimData, fingerprint) {
|
|||
res.completed = true
|
||||
break
|
||||
case ProofFormat.TEXT:
|
||||
for (let index = 0; index < claimData.proof.target.length; index++) {
|
||||
const claimMethod = claimData.proof.target[index]
|
||||
for (let index = 0; index < claimData.claim.length; index++) {
|
||||
const claimMethod = claimData.claim[index]
|
||||
try {
|
||||
res.result = res.result || await containsProof(
|
||||
proofData,
|
||||
|
|
|
@ -44,6 +44,7 @@ describe('asp.parseProfileJws', () => {
|
|||
})
|
||||
it('should return a valid Profile object when provided a valid JWS', async () => {
|
||||
let profile = await asp.parseProfileJws(asp25519ProfileJws, asp25519Uri)
|
||||
console.log(profile);
|
||||
|
||||
expect(profile).to.be.instanceOf(Profile)
|
||||
expect(profile.personas).to.be.length(1)
|
||||
|
|
91
test/claimDefinitions.test.js
Normal file
91
test/claimDefinitions.test.js
Normal file
|
@ -0,0 +1,91 @@
|
|||
/*
|
||||
Copyright 2021 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { expect, use } from 'chai'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
import chaiMatchPattern from 'chai-match-pattern'
|
||||
use(chaiAsPromised)
|
||||
use(chaiMatchPattern)
|
||||
|
||||
const _ = chaiMatchPattern.getLodashModule()
|
||||
|
||||
import { claimDefinitions } from '../src/index.js'
|
||||
|
||||
const pattern = {
|
||||
serviceprovider: {
|
||||
type: _.isString,
|
||||
name: _.isString,
|
||||
},
|
||||
match: {
|
||||
regularExpression: _.isRegExp,
|
||||
isAmbiguous: _.isBoolean,
|
||||
},
|
||||
profile: {
|
||||
display: _.isString,
|
||||
uri: _.isString,
|
||||
qr: (x) => {
|
||||
return _.isString(x) || _.isNull(x)
|
||||
},
|
||||
},
|
||||
proof: {
|
||||
uri: (x) => {
|
||||
return _.isString(x) || _.isNull(x)
|
||||
},
|
||||
request: {
|
||||
fetcher: _.isString,
|
||||
access: _.isString,
|
||||
format: _.isString,
|
||||
data: _.isObject,
|
||||
},
|
||||
},
|
||||
claim: _.isArray
|
||||
}
|
||||
|
||||
claimDefinitions.list.forEach((claimDefName, i) => {
|
||||
const claimDef = claimDefinitions.data[claimDefName]
|
||||
|
||||
describe(`claimDefinitions.${claimDefName}`, () => {
|
||||
it('should be an object', () => {
|
||||
expect(typeof claimDef).to.equal('object')
|
||||
})
|
||||
it('should have a RegExp instance named "reURI"', () => {
|
||||
expect(claimDef.reURI).to.be.instanceof(RegExp)
|
||||
})
|
||||
it('should have a function named "processURI" (1 argument)', () => {
|
||||
expect(claimDef.processURI).to.be.a('function')
|
||||
expect(claimDef.processURI).to.have.length(1)
|
||||
})
|
||||
it('should have an array named "tests"', () => {
|
||||
expect(claimDef.tests).to.be.instanceof(Array)
|
||||
})
|
||||
|
||||
claimDef.tests.forEach((test, j) => {
|
||||
if (test.shouldMatch) {
|
||||
it(`should match "${test.uri}"`, () => {
|
||||
expect(claimDef.reURI.test(test.uri)).to.be.true
|
||||
})
|
||||
it(`should return a valid object for "${test.uri}"`, async () => {
|
||||
const obj = claimDef.processURI(claimDef.tests[0].uri)
|
||||
expect(obj).to.be.a('object')
|
||||
expect(obj).to.matchPattern(pattern)
|
||||
})
|
||||
} else {
|
||||
it(`should not match "${test.uri}"`, () => {
|
||||
expect(claimDef.reURI.test(test.uri)).to.be.false
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
|
@ -17,7 +17,8 @@ import { expect, use } from 'chai'
|
|||
import chaiAsPromised from 'chai-as-promised'
|
||||
use(chaiAsPromised)
|
||||
|
||||
import { openpgp, Profile } from '../src/index.js'
|
||||
import { PublicKey } from 'openpgp'
|
||||
import { keys } from '../src/index.js'
|
||||
|
||||
const pubKeyFingerprint = "3637202523e7c1309ab79e99ef2dc5827b445f4b"
|
||||
const pubKeyEmail = "test@doip.rocks"
|
||||
|
@ -89,110 +90,115 @@ Q+AZdYCbM0hdBjP4xdKZcpqak8ksb+aQFXjGacDL/XN4VrP+tBGxkqIqreoDcgIb
|
|||
=tVW7
|
||||
-----END PGP PUBLIC KEY BLOCK-----`
|
||||
|
||||
describe('openpgp.fetch', () => {
|
||||
describe('keys.fetch', () => {
|
||||
it('should be a function (1 argument)', () => {
|
||||
expect(openpgp.fetch).to.be.a('function')
|
||||
expect(openpgp.fetch).to.have.length(1)
|
||||
expect(keys.fetch).to.be.a('function')
|
||||
expect(keys.fetch).to.have.length(1)
|
||||
})
|
||||
it('should return a Key object when provided a valid fingerprint', async () => {
|
||||
expect(
|
||||
await openpgp.fetch(pubKeyFingerprint)
|
||||
).to.be.instanceOf(Profile)
|
||||
await keys.fetch(pubKeyFingerprint)
|
||||
).to.be.instanceOf(PublicKey)
|
||||
}).timeout('12s')
|
||||
it('should return a Key object when provided a valid email address', async () => {
|
||||
expect(
|
||||
await openpgp.fetch(pubKeyEmail)
|
||||
).to.be.instanceOf(Profile)
|
||||
await keys.fetch(pubKeyEmail)
|
||||
).to.be.instanceOf(PublicKey)
|
||||
}).timeout('12s')
|
||||
it('should reject when provided an invalid email address', () => {
|
||||
return expect(
|
||||
openpgp.fetch('invalid@doip.rocks')
|
||||
keys.fetch('invalid@doip.rocks')
|
||||
).to.eventually.be.rejectedWith('Key does not exist or could not be fetched')
|
||||
}).timeout('12s')
|
||||
})
|
||||
|
||||
describe('openpgp.fetchURI', () => {
|
||||
describe('keys.fetchURI', () => {
|
||||
it('should be a function (1 argument)', () => {
|
||||
expect(openpgp.fetchURI).to.be.a('function')
|
||||
expect(openpgp.fetchURI).to.have.length(1)
|
||||
expect(keys.fetchURI).to.be.a('function')
|
||||
expect(keys.fetchURI).to.have.length(1)
|
||||
})
|
||||
it('should return a Key object when provided a hkp: uri', async () => {
|
||||
expect(
|
||||
await openpgp.fetchURI(`hkp:${pubKeyFingerprint}`)
|
||||
).to.be.instanceOf(Profile)
|
||||
await keys.fetchURI(`hkp:${pubKeyFingerprint}`)
|
||||
).to.be.instanceOf(PublicKey)
|
||||
}).timeout('12s')
|
||||
it('should reject when provided an invalid uri', () => {
|
||||
return expect(
|
||||
openpgp.fetchURI(`inv:${pubKeyFingerprint}`)
|
||||
keys.fetchURI(`inv:${pubKeyFingerprint}`)
|
||||
).to.eventually.be.rejectedWith('Invalid URI protocol')
|
||||
}).timeout('12s')
|
||||
})
|
||||
|
||||
describe('openpgp.fetchHKP', () => {
|
||||
it('should be a function (1 required argument, 1 optional argument)', () => {
|
||||
expect(openpgp.fetchHKP).to.be.a('function')
|
||||
expect(openpgp.fetchHKP).to.have.length(1)
|
||||
describe('keys.fetchHKP', () => {
|
||||
it('should be a function (2 arguments)', () => {
|
||||
expect(keys.fetchHKP).to.be.a('function')
|
||||
expect(keys.fetchHKP).to.have.length(2)
|
||||
})
|
||||
it('should return a Key object when provided a valid fingerprint', async () => {
|
||||
expect(await openpgp.fetchHKP(pubKeyFingerprint)).to.be.instanceOf(
|
||||
Profile
|
||||
expect(await keys.fetchHKP(pubKeyFingerprint)).to.be.instanceOf(
|
||||
PublicKey
|
||||
)
|
||||
}).timeout('12s')
|
||||
it('should return a Key object when provided a valid email address', async () => {
|
||||
expect(await openpgp.fetchHKP(pubKeyEmail)).to.be.instanceOf(
|
||||
Profile
|
||||
expect(await keys.fetchHKP(pubKeyEmail)).to.be.instanceOf(
|
||||
PublicKey
|
||||
)
|
||||
}).timeout('12s')
|
||||
it('should reject when provided an invalid fingerprint', async () => {
|
||||
return expect(
|
||||
openpgp.fetchHKP('4637202523e7c1309ab79e99ef2dc5827b445f4b')
|
||||
keys.fetchHKP('4637202523e7c1309ab79e99ef2dc5827b445f4b')
|
||||
).to.eventually.be.rejectedWith(
|
||||
'Key does not exist or could not be fetched'
|
||||
)
|
||||
}).timeout('12s')
|
||||
it('should reject when provided an invalid email address', async () => {
|
||||
return expect(
|
||||
openpgp.fetchHKP('invalid@doip.rocks')
|
||||
keys.fetchHKP('invalid@doip.rocks')
|
||||
).to.eventually.be.rejectedWith(
|
||||
'Key does not exist or could not be fetched'
|
||||
)
|
||||
}).timeout('12s')
|
||||
})
|
||||
|
||||
describe('openpgp.fetchPlaintext', () => {
|
||||
describe('keys.fetchPlaintext', () => {
|
||||
it('should be a function (1 argument)', () => {
|
||||
expect(openpgp.fetchPlaintext).to.be.a('function')
|
||||
expect(openpgp.fetchPlaintext).to.have.length(1)
|
||||
expect(keys.fetchPlaintext).to.be.a('function')
|
||||
expect(keys.fetchPlaintext).to.have.length(1)
|
||||
})
|
||||
it('should return a Key object', async () => {
|
||||
expect(await openpgp.fetchPlaintext(pubKeyPlaintext)).to.be.instanceOf(
|
||||
Profile
|
||||
expect(await keys.fetchPlaintext(pubKeyPlaintext)).to.be.instanceOf(
|
||||
PublicKey
|
||||
)
|
||||
}).timeout('12s')
|
||||
})
|
||||
|
||||
describe('openpgp.parsePublicKey', () => {
|
||||
describe('keys.process', () => {
|
||||
it('should be a function (1 argument)', () => {
|
||||
expect(openpgp.parsePublicKey).to.be.a('function')
|
||||
expect(openpgp.parsePublicKey).to.have.length(1)
|
||||
expect(keys.process).to.be.a('function')
|
||||
expect(keys.process).to.have.length(1)
|
||||
})
|
||||
it('should return an object with specific openpgp', async () => {
|
||||
const pubKey = await openpgp.fetchPlaintext(pubKeyPlaintext)
|
||||
const profile = await openpgp.parsePublicKey(pubKey.publicKey.key)
|
||||
expect(profile).to.be.instanceOf(Profile)
|
||||
it('should return an object with specific keys', async () => {
|
||||
const pubKey = await keys.fetchPlaintext(pubKeyPlaintext)
|
||||
const obj = await keys.process(pubKey)
|
||||
expect(obj).to.have.keys([
|
||||
'users',
|
||||
'fingerprint',
|
||||
'primaryUserIndex',
|
||||
'key',
|
||||
])
|
||||
})
|
||||
it('should ignore non-proof notations', async () => {
|
||||
const pubKey = await openpgp.fetchPlaintext(pubKeyWithOtherNotations)
|
||||
const profile = await openpgp.parsePublicKey(pubKey.publicKey.key)
|
||||
expect(profile.personas).to.be.lengthOf(1)
|
||||
expect(profile.personas[0].claims).to.be.lengthOf(1)
|
||||
expect(profile.personas[0].claims[0].uri).to.be.equal('dns:yarmo.eu?type=TXT')
|
||||
const pubKey = await keys.fetchPlaintext(pubKeyWithOtherNotations)
|
||||
const obj = await keys.process(pubKey)
|
||||
expect(obj.users).to.be.lengthOf(1)
|
||||
expect(obj.users[0].claims).to.be.lengthOf(1)
|
||||
expect(obj.users[0].claims[0].uri).to.be.equal('dns:yarmo.eu?type=TXT')
|
||||
})
|
||||
it('should properly handle revoked UIDs', async () => {
|
||||
const pubKey = await openpgp.fetchPlaintext(pubKeyWithRevokedUID)
|
||||
const profile = await openpgp.parsePublicKey(pubKey.publicKey.key)
|
||||
expect(profile.personas).to.be.lengthOf(2)
|
||||
expect(profile.personas[0].isRevoked).to.be.true
|
||||
expect(profile.personas[1].isRevoked).to.be.false
|
||||
const pubKey = await keys.fetchPlaintext(pubKeyWithRevokedUID)
|
||||
const obj = await keys.process(pubKey)
|
||||
expect(obj.users).to.be.lengthOf(2)
|
||||
expect(obj.users[0].userData.isRevoked).to.be.true
|
||||
expect(obj.users[1].userData.isRevoked).to.be.false
|
||||
})
|
||||
})
|
|
@ -1,56 +0,0 @@
|
|||
/*
|
||||
Copyright 2021 Yarmo Mackenbach
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { expect, use } from 'chai'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
use(chaiAsPromised)
|
||||
|
||||
import { ServiceProviderDefinitions, ServiceProvider } from '../src/index.js'
|
||||
|
||||
ServiceProviderDefinitions.list.forEach((spDefName, i) => {
|
||||
const spDef = ServiceProviderDefinitions.data[spDefName]
|
||||
|
||||
describe(`ServiceProviderDefinitions.${spDefName}`, () => {
|
||||
it('should be an object', () => {
|
||||
expect(typeof spDef).to.equal('object')
|
||||
})
|
||||
it('should have a RegExp instance named "reURI"', () => {
|
||||
expect(spDef.reURI).to.be.instanceof(RegExp)
|
||||
})
|
||||
it('should have a function named "processURI" (1 argument)', () => {
|
||||
expect(spDef.processURI).to.be.a('function')
|
||||
expect(spDef.processURI).to.have.length(1)
|
||||
})
|
||||
it('should have an array named "tests"', () => {
|
||||
expect(spDef.tests).to.be.instanceof(Array)
|
||||
})
|
||||
|
||||
spDef.tests.forEach((test, j) => {
|
||||
if (test.shouldMatch) {
|
||||
it(`should match "${test.uri}"`, () => {
|
||||
expect(spDef.reURI.test(test.uri)).to.be.true
|
||||
})
|
||||
it(`should return a valid object for "${test.uri}"`, async () => {
|
||||
const obj = spDef.processURI(spDef.tests[0].uri)
|
||||
expect(obj).to.be.instanceOf(ServiceProvider)
|
||||
})
|
||||
} else {
|
||||
it(`should not match "${test.uri}"`, () => {
|
||||
expect(spDef.reURI.test(test.uri)).to.be.false
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
|
@ -13,11 +13,9 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
import { expect, use } from 'chai'
|
||||
import chaiAsPromised from 'chai-as-promised'
|
||||
use(chaiAsPromised)
|
||||
import { expect } from 'chai'
|
||||
|
||||
import { Profile, signatures } from '../src/index.js'
|
||||
import { signatures } from '../src/index.js'
|
||||
|
||||
const sigProfile = `-----BEGIN PGP SIGNED MESSAGE-----
|
||||
Hash: SHA512
|
||||
|
@ -82,29 +80,28 @@ YCKJPotiqe50nBijHHbuABtBianiMZOm2BbaPnsmdHIX5ynWhOI8LHR1CVmTI/0o
|
|||
=2vuM
|
||||
-----END PGP SIGNATURE-----`
|
||||
|
||||
describe('signatures.parse', () => {
|
||||
describe('signatures.process', () => {
|
||||
it('should be a function (2 arguments)', () => {
|
||||
expect(signatures.parse).to.be.a('function')
|
||||
expect(signatures.parse).to.have.length(1)
|
||||
expect(signatures.process).to.be.a('function')
|
||||
expect(signatures.process).to.have.length(1)
|
||||
})
|
||||
it('should verify a valid signature', async () => {
|
||||
const profile = await signatures.parse(sigProfile)
|
||||
expect(profile).to.be.instanceOf(Profile)
|
||||
expect(profile.identifier).to.be.equal(
|
||||
'openpgp4fpr:3637202523e7c1309ab79e99ef2dc5827b445f4b'
|
||||
const verification = await signatures.process(sigProfile)
|
||||
expect(verification.fingerprint).to.be.equal(
|
||||
'3637202523e7c1309ab79e99ef2dc5827b445f4b'
|
||||
)
|
||||
expect(profile.personas[0].claims).to.be.length(1)
|
||||
expect(verification.users[0].claims).to.be.length(1)
|
||||
})
|
||||
it('should reject an invalid signature', async () => {
|
||||
return expect(
|
||||
signatures.parse(invalidSigProfileMessage)
|
||||
signatures.process(invalidSigProfileMessage)
|
||||
).to.eventually.be.rejectedWith(
|
||||
'Signature could not be verified (Signed digest did not match)'
|
||||
)
|
||||
})
|
||||
it('should reject an invalid signature', async () => {
|
||||
return expect(
|
||||
signatures.parse(invalidSigProfileHash)
|
||||
signatures.process(invalidSigProfileHash)
|
||||
).to.eventually.be.rejectedWith(
|
||||
'Signature could not be read (Ascii armor integrity check failed)'
|
||||
)
|
||||
|
|
|
@ -59,10 +59,10 @@ describe('utils.generateProxyURL', () => {
|
|||
}
|
||||
expect(
|
||||
utils.generateProxyURL('http', { domain: 'domain.org' }, opts)
|
||||
).to.equal('https://localhost/api/3/get/http?domain=domain.org')
|
||||
).to.equal('https://localhost/api/2/get/http?domain=domain.org')
|
||||
expect(
|
||||
utils.generateProxyURL('dns', { domain: 'domain.org' }, opts)
|
||||
).to.equal('https://localhost/api/3/get/dns?domain=domain.org')
|
||||
).to.equal('https://localhost/api/2/get/dns?domain=domain.org')
|
||||
})
|
||||
it('should generate correct proxy URLs for explicit http scheme', () => {
|
||||
const opts = {
|
||||
|
@ -73,10 +73,10 @@ describe('utils.generateProxyURL', () => {
|
|||
}
|
||||
expect(
|
||||
utils.generateProxyURL('http', { domain: 'domain.org' }, opts)
|
||||
).to.equal('http://localhost/api/3/get/http?domain=domain.org')
|
||||
).to.equal('http://localhost/api/2/get/http?domain=domain.org')
|
||||
expect(
|
||||
utils.generateProxyURL('dns', { domain: 'domain.org' }, opts)
|
||||
).to.equal('http://localhost/api/3/get/dns?domain=domain.org')
|
||||
).to.equal('http://localhost/api/2/get/dns?domain=domain.org')
|
||||
})
|
||||
it('should generate correct proxy URLs for default scheme', () => {
|
||||
const opts = {
|
||||
|
@ -86,10 +86,10 @@ describe('utils.generateProxyURL', () => {
|
|||
}
|
||||
expect(
|
||||
utils.generateProxyURL('http', { domain: 'domain.org' }, opts)
|
||||
).to.equal('https://localhost/api/3/get/http?domain=domain.org')
|
||||
).to.equal('https://localhost/api/2/get/http?domain=domain.org')
|
||||
expect(
|
||||
utils.generateProxyURL('dns', { domain: 'domain.org' }, opts)
|
||||
).to.equal('https://localhost/api/3/get/dns?domain=domain.org')
|
||||
).to.equal('https://localhost/api/2/get/dns?domain=domain.org')
|
||||
})
|
||||
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ import { expect, use } from 'chai'
|
|||
import chaiAsPromised from 'chai-as-promised'
|
||||
use(chaiAsPromised)
|
||||
|
||||
import { ServiceProviderDefinitions, verifications } from '../src/index.js'
|
||||
import { claimDefinitions, verifications } from '../src/index.js'
|
||||
|
||||
const fingerprint = '3637202523e7c1309ab79e99ef2dc5827b445f4b'
|
||||
const plaintextCorrectProofData = [
|
||||
|
@ -44,14 +44,12 @@ const bcryptIncorrectProofData = [
|
|||
const bcryptCostlyProofData = [
|
||||
'$2y$16$4Knuu11ZyPXa1qxEbEsKQemKY6ZHM8Bk7WElYfL8q5kmzNjY1Ty8W'
|
||||
]
|
||||
const claimData = ServiceProviderDefinitions.data.irc.processURI('irc://domain.tld/test')
|
||||
const claimData = claimDefinitions.data.irc.processURI('irc://domain.tld/test')
|
||||
|
||||
describe('verifications.run', () => {
|
||||
it('should verify a plaintext proof', async () => {
|
||||
const result = await verifications.run(plaintextCorrectProofData, claimData, fingerprint)
|
||||
expect(result.result).to.be.true
|
||||
const result2 = await verifications.run(plaintextCorrectProofData, claimData, fingerprint.toUpperCase())
|
||||
expect(result2.result).to.be.true
|
||||
})
|
||||
// issue #22
|
||||
it('should handle a plaintext proof with whitespace', async () => {
|
||||
|
@ -65,8 +63,6 @@ describe('verifications.run', () => {
|
|||
it('should verify a argon2-hashed proof', async () => {
|
||||
const result = await verifications.run(argon2CorrectProofData, claimData, fingerprint)
|
||||
expect(result.result).to.be.true
|
||||
const result2 = await verifications.run(argon2CorrectProofData, claimData, fingerprint.toUpperCase())
|
||||
expect(result2.result).to.be.true
|
||||
})
|
||||
it('should reject a wrong argon2-hashed proof', async () => {
|
||||
const result = await verifications.run(argon2IncorrectProofData, claimData, fingerprint)
|
||||
|
@ -75,8 +71,6 @@ describe('verifications.run', () => {
|
|||
it('should verify a bcrypt-hashed proof', async () => {
|
||||
const result = await verifications.run(bcryptCorrectProofData, claimData, fingerprint)
|
||||
expect(result.result).to.be.true
|
||||
const result2 = await verifications.run(bcryptCorrectProofData, claimData, fingerprint.toUpperCase())
|
||||
expect(result2.result).to.be.true
|
||||
})
|
||||
it('should reject a wrong bcrypt-hashed proof', async () => {
|
||||
const result = await verifications.run(bcryptIncorrectProofData, claimData, fingerprint)
|
||||
|
|
Loading…
Reference in a new issue