Compare commits

..

78 commits

Author SHA1 Message Date
Ty
0f43a7b9e3
Update for publishing 2024-06-14 22:50:22 -06:00
Ty
2153185729
Add sasl support 2024-06-14 22:15:42 -06:00
Ty
a523ec4191
Update to yarn modern 2024-06-14 22:15:21 -06:00
André Jaenisch
fa57e7b538 feat: add discord support
bump discord API version to v10

accept discord.gg urls

allow proof in guild name

add comments

Reviewed-on: https://codeberg.org/keyoxide/doipjs/pulls/85
2024-04-08 13:44:44 +00:00
Bram Hagens
758255f652
add comments 2024-04-08 00:22:44 +02:00
Bram Hagens
fff5ce4aca
Merge remote-tracking branch 'upstream/dev' into support-discord 2024-04-08 00:05:52 +02:00
Ty
06ea6732de
Add pronouns.cc verification 2024-03-22 18:44:49 -06:00
Bram Hagens
041e22c52d
allow proof in guild name 2024-02-08 10:36:18 +01:00
Bram Hagens
6d464176df
accept discord.gg urls 2024-02-08 10:36:18 +01:00
Bram Hagens
9b1a5d4d26
bump discord API version to v10 2024-02-08 10:11:52 +01:00
Bram Hagens
689117ac98
feat: add discord support 2024-02-08 01:04:16 +01:00
Yarmo Mackenbach
6d2606c8a9
chore: release 1.2.9 2024-02-01 17:06:40 +01:00
Dario Vladovic
fecaf7df12 feat: remove unused dependencies 2024-01-31 02:47:48 +00:00
Yarmo Mackenbach
c52632cbb6
feat: change jsdoc theme 2024-01-30 01:13:36 +01:00
Yarmo Mackenbach
f8d0422443
fix: review 2024-01-29 14:05:17 +01:00
Yarmo Mackenbach
fe6588dcbb
feat: combine jsdoc with tsimport 2024-01-29 13:19:08 +01:00
Yarmo Mackenbach
deaa858345
feat: improve jsdoc for documentation 2024-01-29 13:19:07 +01:00
Yarmo Mackenbach
336029fd87 fix: fix jsdoc errors found by eslint 2024-01-28 12:55:45 +00:00
Yarmo Mackenbach
3f8579513a feat: add jsdoc plugin to eslint 2024-01-28 12:55:45 +00:00
Yarmo Mackenbach
2ef792fbbb fix: remove TS import types 2024-01-28 12:55:45 +00:00
Yarmo Mackenbach
edc3f401bc
chore: update README [SKIP CI] 2024-01-28 10:20:34 +01:00
Yarmo Mackenbach
7115ecbfe6
fix: fix issue template label [SKIP CI] 2024-01-26 23:09:01 +01:00
Yarmo Mackenbach
d672ea2328
fix: fix issue template label [SKIP CI] 2024-01-26 19:28:36 +01:00
Yarmo Mackenbach
d7d9556165
chore: add issue templates [SKIP CI] 2024-01-26 18:28:27 +01:00
Yarmo Mackenbach
6b8fd0b099 Merge pull request 'support-orcid-claim' (#47) from lepus2589/doipjs:support-orcid-claim into dev
Reviewed-on: https://codeberg.org/keyoxide/doipjs/pulls/47
2024-01-25 21:14:11 +00:00
Tim Haase
7fe9d29690 Add ORCID biography as possible proof location 2024-01-25 21:14:11 +00:00
Tim Haase
0dd0d91104 feat: Add ORCID service provider 2024-01-25 21:14:11 +00:00
Yarmo Mackenbach
626077f880
fix: fix bad reference 2024-01-23 22:55:50 +01:00
Yarmo Mackenbach
7639962521
fix: optimize regexp creation 2024-01-23 20:02:02 +01:00
Yarmo Mackenbach
a9c1ffabfe
fix: move clearTimeout to finally() 2024-01-23 19:58:26 +01:00
Yarmo Mackenbach
c02742e041
chore: release 1.2.8 2024-01-23 19:08:03 +01:00
Yarmo Mackenbach
70ee790f0a Merge pull request 'Support OpenPGP/ASPE claims' (#66) from support-openpgp-aspe-claims into dev
Reviewed-on: https://codeberg.org/keyoxide/doipjs/pulls/66
2024-01-23 17:53:19 +00:00
Yarmo Mackenbach
4a77591c57
feat: support ASPE claims 2024-01-23 15:54:57 +01:00
Yarmo Mackenbach
cd96131ad1
feat: support OpenPGP claims 2024-01-23 13:56:15 +01:00
Yarmo Mackenbach
dd2c134a75
chore: bump deps 2024-01-23 00:27:11 +01:00
Yarmo Mackenbach
7eaaedc12a
chore: remove obsolete file 2024-01-23 00:18:32 +01:00
Yarmo Mackenbach
468f150509
chore: bump deps 2024-01-23 00:14:15 +01:00
Yarmo Mackenbach
462348dced
chore: release 1.2.7 2023-10-09 19:37:29 +02:00
Yarmo Mackenbach
f724e81c06
fix: fix regex errors 2023-10-09 19:34:44 +02:00
Yarmo Mackenbach
9bdc0a639f
fix: fix CI syntax 2023-10-09 19:07:29 +02:00
Yarmo Mackenbach
3c4f61ada1
chore: release 1.2.6 2023-10-09 16:28:23 +02:00
Yarmo Mackenbach
2687742e23
feat: add github proof location 2023-10-09 16:21:06 +02:00
Yarmo Mackenbach
056ebd6d83
fix: fix IRC profile display value 2023-10-09 16:18:47 +02:00
Yarmo Mackenbach
34ad2d718d
fix: update lobste.rs URL syntax 2023-10-07 12:49:35 +02:00
Yarmo Mackenbach
a616dcd66d
fix: make IRC fetcher compatible with aspe 2023-10-07 12:39:22 +02:00
Yarmo Mackenbach
8cd40ea422
feat: add JSON schemas 2023-10-06 21:40:45 +02:00
Yarmo Mackenbach
8a01cdf9fa
chore: release 1.2.5 2023-10-05 11:14:38 +02:00
Yarmo Mackenbach
a3c9e9137d
feat: support themeColor 2023-10-05 11:11:25 +02:00
Yarmo Mackenbach
2dd18395dd
chore: release 1.2.4 2023-10-04 09:13:43 +02:00
Yarmo Mackenbach
fc0af3dd1d
feat: modify display object 2023-10-04 09:11:02 +02:00
Yarmo Mackenbach
34688bb644
chore: release 1.2.3 2023-10-03 13:47:26 +02:00
Yarmo Mackenbach
c5d6c2c9d9
fix: ambiguity logic 2023-10-03 13:45:05 +02:00
Yarmo Mackenbach
0d1e400d02
chore: release 1.2.2 2023-10-03 12:05:09 +02:00
Yarmo Mackenbach
7e5d15ac0f
fix: display data logic in claim toJSON 2023-09-25 16:42:16 +02:00
Yarmo Mackenbach
c1cb3fcbb6
fix: fix service provider information 2023-09-25 11:55:59 +02:00
Yarmo Mackenbach
a25e94002c
chore: release 1.2.1 2023-09-23 10:28:24 +02:00
Yarmo Mackenbach
bd864b796d
chore: release 1.2.0 2023-09-23 10:16:41 +02:00
Yarmo Mackenbach
ea8eb234ad
feat: support forgejo 2023-09-23 09:45:15 +02:00
Yarmo Mackenbach
264645b381
feat: add verification result validation function 2023-09-23 09:44:51 +02:00
Yarmo Mackenbach
ba6941448c
chore: release 1.1.1 2023-09-22 11:00:36 +02:00
Yarmo Mackenbach
beb78e8227
fix: normalize case before hash verification 2023-09-22 08:59:33 +02:00
Yarmo Mackenbach
9c9b387fc9
chore: release 1.1.0 2023-09-21 14:49:15 +02:00
Yarmo Mackenbach
cb397e42b7
feat: unify fromJSON() for Profile, Persona and Claim 2023-09-21 14:26:26 +02:00
Yarmo Mackenbach
4aba882b1b
chore: release 1.0.4 2023-09-19 15:35:55 +02:00
Yarmo Mackenbach
1baffe9c92
fix: allow the activitypub Person request to fail 2023-09-19 15:31:44 +02:00
Yarmo Mackenbach
feda782136
chore: release 1.0.3 2023-09-19 13:37:41 +02:00
Yarmo Mackenbach
81d5ba0d57
fix: avoid using potentially missing URL 2023-09-19 13:35:11 +02:00
Yarmo Mackenbach
cae1020f45
chore: release 1.0.2 2023-09-19 12:57:57 +02:00
Yarmo Mackenbach
c2f9efa698
fix: make nodeinfo requests use HTTPS 2023-09-19 12:54:37 +02:00
Yarmo Mackenbach
549a86c121
chore: release 1.0.1 2023-09-18 17:46:54 +02:00
Yarmo Mackenbach
d8529a7f92
feat: add prerelease checks script 2023-09-18 17:34:54 +02:00
Yarmo Mackenbach
2a314bef52
fix: fix import calls preventing proper bundling 2023-09-18 12:49:35 +02:00
Yarmo Mackenbach
8ea07938e4 feat: use nodeinfo for fediverse instance info 2023-09-18 08:18:17 +00:00
Yarmo Mackenbach
cb841fe9b7 feat: better support both Person and Note data 2023-09-18 08:18:17 +00:00
Yarmo Mackenbach
09b052c7b9
fix: ignore OpenPGP users without userId 2023-09-17 11:39:00 +02:00
Yarmo Mackenbach
0dfb0bc38a
fix: fix ci syntax 2023-09-14 18:20:09 +02:00
Preston Maness
027d7e1a4b Use more generic Account entity rather than Collective entity
As per

https://graphql-docs-v2.opencollective.com/types/Account

>Account interface shared by all kind of accounts (Bot, Collective,
Event, User, Organization)
2023-08-16 16:34:49 -05:00
Yarmo Mackenbach
75ec28b618
chore: update CHANGELOG 2023-07-13 10:53:43 +02:00
79 changed files with 90188 additions and 37964 deletions

View file

@ -4,7 +4,10 @@
"es2021": true,
"node": true
},
"extends": "standard",
"extends": [
"standard",
"plugin:jsdoc/recommended"
],
"overrides": [
],
"parserOptions": {
@ -12,5 +15,8 @@
"sourceType": "module"
},
"rules": {
}
},
"plugins": [
"jsdoc"
]
}

View file

@ -0,0 +1,16 @@
---
name: 'Bug'
about: 'Report a bug'
title: '[BUG] '
ref: 'dev'
labels:
- 'Status/Needs Triage'
- Type/Bug
---
### What happened
### Proposed solutions

View file

@ -0,0 +1,22 @@
---
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

View file

@ -0,0 +1,38 @@
---
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)

View file

@ -1,8 +1,8 @@
pipeline:
prepare:
when:
when:
branch: main
event: tag
steps:
prepare:
image: node
commands:
- yarn --pure-lockfile

View file

@ -1,4 +1,4 @@
pipeline:
steps:
test:
image: node
commands:

BIN
.yarn/install-state.gz Normal file

Binary file not shown.

6
.yarnrc.yml Normal file
View file

@ -0,0 +1,6 @@
nodeLinker: node-modules
npmScopes:
myriation:
npmPublishRegistry: https://git.myriation.xyz/api/packages/myriation/npm/
npmAlwaysAuth: true
npmAuthToken: REPLACE-ME

View file

@ -6,6 +6,92 @@ 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

View file

@ -1,22 +1,18 @@
# 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 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).
[doip.js](https://codeberg.org/keyoxide/doipjs) allows websites and Node.js projects to verify decentralized online
identities.
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**:
@ -56,32 +52,32 @@ const verifyIdentity = async (url, fp) => {
verifyIdentity('dns:doip.rocks', '9f0048ac0b23301e1f77e994909f6bd6f80f485d')
```
This snippet works and will verify the [doip.rocks](https://doip.rocks) domain as
This snippet verifies the [doip.rocks](https://doip.rocks) domain as
bidirectionally linked to Yarmo's cryptographic key.
## About Keyoxide
## Contributing
[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.
Anyone can contribute!
## Community
Developers are invited to:
There's a [Keyoxide Matrix room](https://matrix.to/#/#keyoxide:matrix.org) where
we discuss everything DOIP and Keyoxide.
- fork the repository and play around
- submit PRs to [implement new features or fix bugs](https://codeberg.org/keyoxide/doipjs/issues)
## Donate
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.
Please consider [donating](https://liberapay.com/Keyoxide/) if you think this
project is a step in the right direction for the internet.
Everyone is invited to:
## Funding
- 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
This library was realized with funding from
[NLnet](https://nlnet.nl/project/Keyoxide/).
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.

8989
dist/doip.core.js vendored

File diff suppressed because one or more lines are too long

18
dist/doip.core.min.js vendored

File diff suppressed because one or more lines are too long

15343
dist/doip.fetchers.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,8 @@
{
"plugins": ["plugins/markdown"],
"plugins": [
"plugins/markdown",
"node_modules/jsdoc-tsimport-plugin"
],
"source": {
"include": ["./src", "./README.md"]
},
@ -12,21 +15,26 @@
}
},
"opts": {
"template": "node_modules/clean-jsdoc-theme",
"theme_opts": {
"theme": "light",
"menu": [
{
"title": "Source code",
"link": "https://codeberg.org/keyoxide/doipjs",
"target": "_blank"
"template": "node_modules/docdash",
"destination": "docs/"
},
{
"title": "Keyoxide",
"link": "https://keyoxide.org",
"target": "_blank"
"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"
}
]
}
}
}

View file

@ -1,6 +1,6 @@
{
"name": "doipjs",
"version": "1.0.0",
"name": "@myriation/doipjs",
"version": "1.2.9+myriaiton.1",
"description": "Decentralized Online Identity Proofs library in Node.js",
"type": "module",
"main": "./src/index.js",
@ -15,18 +15,15 @@
"default": "./src/fetcher/index.minimal.js"
}
},
"packageManager": "yarn@1.22.19",
"packageManager": "yarn@4.3.0",
"dependencies": {
"@openpgp/hkp-client": "^0.0.3",
"@openpgp/wkd-client": "^0.0.4",
"@xmpp/client": "^0.13.1",
"@xmpp/debug": "^0.13.0",
"axios": "^0.25.0",
"axios": "^1.6.5",
"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",
@ -42,25 +39,26 @@
"@rollup/plugin-node-resolve": "^15.1.0",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"clean-jsdoc-theme": "^3.2.4",
"docdash": "^2.0.2",
"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": "^3.6.6",
"jsdoc": "^4.0.2",
"jsdoc-tsimport-plugin": "^1.0.5",
"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": "yarn run test && yarn run build",
"release": "node ./prerelease.js && 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",
@ -75,7 +73,7 @@
},
"repository": {
"type": "git",
"url": "https://codeberg.org/keyoxide/doipjs"
"url": "https://git.myriation.org/myriation/doipjs"
},
"homepage": "https://js.doip.rocks",
"keywords": [

33
prerelease.js Normal file
View file

@ -0,0 +1,33 @@
/*
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()

View file

@ -1,8 +0,0 @@
{
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}

View file

@ -32,9 +32,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>}
* @returns {Promise<Profile>} The fetched profile
* @example
* const key = doip.aspe.fetchASPE('aspe:domain.tld:1234567890');
* const key = await doip.aspe.fetchASPE('aspe:domain.example:1234567890');
*/
export async function fetchASPE (uri) {
const re = /aspe:(.*):(.*)/
@ -76,13 +76,13 @@ export async function fetchASPE (uri) {
}
/**
* Fetch a public key using Web Key Directory
* Parse a JWS and extract the profile it contains
* @function
* @param {string} profileJws - Compact-Serialized profile JWS
* @param {string} uri - The ASPE URI associated with the profile
* @returns {Promise<Profile>}
* @returns {Promise<Profile>} The extracted profile
* @example
* const key = doip.aspe.parseProfileJws('...');
* const key = await doip.aspe.parseProfileJws('...', 'aspe:domain.example:123');
*/
export async function parseProfileJws (profileJws, uri) {
const matches = uri.match(/aspe:(.*):(.*)/)
@ -130,7 +130,9 @@ export async function parseProfileJws (profileJws, uri) {
const profileName = payloadJson['http://ariadne.id/name']
/** @type {string} */
const profileDescription = payloadJson['http://ariadne.id/description']
/** @type {string[]} */
/** @type {string} */
const profileThemeColor = payloadJson['http://ariadne.id/color']
/** @type {Array<string>} */
const profileClaims = payloadJson['http://ariadne.id/claims']
const profileClaimsParsed = profileClaims.map(x => new Claim(x, uri))
@ -139,6 +141,9 @@ export async function parseProfileJws (profileJws, uri) {
if (profileDescription) {
pe.setDescription(profileDescription)
}
if (profileThemeColor && /^#([0-9A-F]{3}){1,2}$/i.test(profileThemeColor)) {
pe.themeColor = profileThemeColor
}
const profile = new Profile(ProfileType.ASP, uri, [pe])
profile.publicKey.fingerprint = fp
@ -164,10 +169,10 @@ export async function parseProfileJws (profileJws, uri) {
}
/**
* Compute the fingerprint for JWK keys
* Compute the fingerprint for {@link https://github.com/panva/jose/blob/main/docs/interfaces/types.JWK.md JWK} keys
* @function
* @param {import('jose').JWK} key
* @returns {Promise<string>}
* @param {import('jose').JWK} key - The JWK public key for which to compute the fingerprint
* @returns {Promise<string>} The computed fingerprint
*/
export async function computeJwkFingerprint (key) {
const thumbprint = await calculateJwkThumbprint(key, 'sha512')

View file

@ -30,18 +30,17 @@ import { ServiceProvider } from './serviceProvider.js'
* @property {string} fingerprint - The fingerprint to verify the claim against
* @property {number} status - The current status code of the claim
* @property {Array<object>} matches - The claim definitions matched against the URI
*/
export class Claim {
/**
* Initialize a Claim object
* @constructor
* @param {string} [uri] - The URI of the identity claim
* @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');
*/
export class Claim {
/**
* Initialize a Claim object
* @param {string} [uri] - The URI of the identity claim
* @param {string} [fingerprint] - The fingerprint of the OpenPGP key
*/
constructor (uri, fingerprint) {
// Verify validity of URI
if (uri && !isUri(uri)) {
@ -71,18 +70,20 @@ export class Claim {
*/
this._status = ClaimStatus.INIT
/**
* @type {import('./serviceProvider.js').ServiceProvider[]}
* @type {Array<ServiceProvider>}
*/
this._matches = []
}
/**
* @function
* @param {object} claimObject
* @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
* const claimAlt = doip.Claim(JSON.stringify(claim));
* doip.Claim.fromJSON(JSON.stringify(claim));
*/
static fromJson (claimObject) {
static fromJSON (claimObject) {
/** @type {Claim} */
let claim
let result
@ -214,9 +215,8 @@ 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 {object} [opts] - Options for proxy, fetchers
* @param {import('./types').VerificationConfig} [opts] - Options for proxy, fetchers
*/
async verify (opts) {
if (this._status === ClaimStatus.INIT) {
@ -244,6 +244,7 @@ export class Claim {
let claimData = this._matches[index]
/** @type {import('./types').VerificationResult | null} */
let verificationResult = null
let proofData = null
let proofFetchError
@ -266,11 +267,18 @@ export class Claim {
viaProxy: proofData.viaProxy
}
// Post process the data
// 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
if (def.functions?.postprocess) {
try {
({ claimData, proofData } = def.functions.postprocess(claimData, proofData))
({ claimData, proofData } = await def.functions.postprocess(claimData, proofData, opts))
} catch (_) {}
}
} else {
@ -278,7 +286,7 @@ export class Claim {
verificationResult = verificationResult || {
result: false,
completed: true,
proof: {},
proof: null,
errors: [proofFetchError]
}
}
@ -302,15 +310,16 @@ 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}
* @returns {boolean} Whether the claim is ambiguous
*/
isAmbiguous () {
if (this._status === ClaimStatus.INIT) {
if (this._status < ClaimStatus.MATCHED) {
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
}
@ -318,21 +327,21 @@ export class Claim {
* Get a JSON representation of the Claim object. Useful when transferring
* data between instances/machines.
* @function
* @returns {object}
* @returns {object} JSON reprentation of the claim
*/
toJSON () {
let displayName = this._uri
let displayUrl = null
let displayProfileName = this._uri
let displayProfileUrl = null
let displayProofUrl = null
let displayServiceProviderName = null
let displayServiceProviderId = null
if (this._status >= 200 && this._status < 300) {
displayName = this._matches[0].profile.display
displayUrl = this._matches[0].profile.uri
displayServiceProviderName = this._matches[0].about.name
} else if (this._status === ClaimStatus.MATCHED && !this.isAmbiguous()) {
displayName = this._matches[0].profile.display
displayUrl = this._matches[0].profile.uri
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 {
@ -342,17 +351,20 @@ export class Claim {
matches: this._matches.map(x => x.toJSON()),
status: this._status,
display: {
name: displayName,
url: displayUrl,
serviceProviderName: displayServiceProviderName
profileName: displayProfileName,
profileUrl: displayProfileUrl,
proofUrl: displayProofUrl,
serviceProviderName: displayServiceProviderName,
serviceProviderId: displayServiceProviderId
}
}
}
}
/**
* @param {object} claimObject
* @returns {Claim | Error}
* @ignore
* @param {object} claimObject - JSON representation of a claim
* @returns {Claim | Error} Parsed claim
*/
function importJsonClaimVersion1 (claimObject) {
if (!('claimVersion' in claimObject && claimObject.claimVersion === 1)) {
@ -392,8 +404,9 @@ function importJsonClaimVersion1 (claimObject) {
}
/**
* @param {object} claimObject
* @returns {Claim | Error}
* @ignore
* @param {object} claimObject - JSON representation of a claim
* @returns {Claim | Error} Parsed claim
*/
function importJsonClaimVersion2 (claimObject) {
if (!('claimVersion' in claimObject && claimObject.claimVersion === 2)) {

View file

@ -22,4 +22,4 @@ limitations under the License.
* doip.js library version
* @constant {string}
*/
export const version = '1.0.0'
export const version = '1.2.9+myriaiton.1'

View file

@ -21,26 +21,8 @@ import { ProxyPolicy } from './enums.js'
*/
/**
* 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
* The default claim verification config used throughout the library
* @type {import('./types').VerificationConfig}
*/
export const opts = {
proxy: {
@ -53,7 +35,8 @@ export const opts = {
privateKey: null
},
irc: {
nick: null
nick: null,
sasl: []
},
matrix: {
instance: null,

View file

@ -40,6 +40,8 @@ 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 */
@ -50,6 +52,8 @@ 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 */
@ -111,7 +115,7 @@ export const ClaimFormat = {
}
/**
* How to find the claim inside the proof's JSON data
* How to find the proof inside the fetched data
* @readonly
* @enum {string}
*/
@ -197,3 +201,13 @@ export const PublicKeyFetchMethod = {
HTTP: 'http',
NONE: 'none'
}
/**
* Protocol to query OpenPGP public keys
* @readonly
* @enum {string}
*/
export const OpenPgpQueryProtocol = {
HKP: 'hkp',
WKD: 'wkd'
}

View file

@ -13,27 +13,36 @@ 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
* @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>}
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
* @returns {Promise<object>} The fetched ActivityPub object
*/
export async function fn (data, opts) {
let timeoutHandle
@ -89,8 +98,7 @@ export async function fn (data, opts) {
})()
})
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
clearTimeout(timeoutHandle)
return result
})
}

90
src/fetcher/aspe.js Normal file
View file

@ -0,0 +1,90 @@
/*
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)
})
}

View file

@ -13,19 +13,33 @@ 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
* @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>}
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
* @returns {Promise<object>} The fetched DNS records
*/
export async function fn (data, opts) {
if (isBrowser) {
@ -56,8 +70,7 @@ export async function fn (data, opts) {
})
})
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
clearTimeout(timeoutHandle)
return result
})
}

View file

@ -13,20 +13,34 @@ 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
* @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>}
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
* @returns {Promise<object>} The fetched GraphQL object
*/
export async function fn (data, opts) {
let timeoutHandle
@ -68,8 +82,7 @@ export async function fn (data, opts) {
})
})
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
clearTimeout(timeoutHandle)
return result
})
}

View file

@ -13,21 +13,35 @@ 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
* @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>}
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
* @returns {Promise<object|string>} The fetched JSON object or text
*/
export async function fn (data, opts) {
let timeoutHandle
@ -83,8 +97,7 @@ export async function fn (data, opts) {
}
})
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
clearTimeout(timeoutHandle)
return result
})
}

View file

@ -14,10 +14,12 @@ 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'

View file

@ -14,7 +14,9 @@ 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'

View file

@ -13,24 +13,34 @@ 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
* @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 {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>}
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
* @returns {Promise<Array<string>>} The fetched proofs from an IRC account
*/
export async function fn (data, opts) {
let timeoutHandle
@ -49,14 +59,27 @@ 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
debug: false,
...saslOptions
})
const reKey = /[a-zA-Z0-9\-_]+\s+:\s(openpgp4fpr:.*)/
const reKey = /[a-zA-Z0-9\-_]+\s+:\s((?:openpgp4fpr|aspe):.*)/
const reEnd = /End\sof\s.*\staxonomy./
const keys = []
@ -80,8 +103,7 @@ export async function fn (data, opts) {
}
})
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
clearTimeout(timeoutHandle)
return result
})
}

View file

@ -13,27 +13,36 @@ 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
* @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>}
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
* @returns {Promise<object>} The fetched Matrix object
*/
export async function fn (data, opts) {
let timeoutHandle
@ -72,8 +81,7 @@ export async function fn (data, opts) {
})
})
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
clearTimeout(timeoutHandle)
return result
})
}

131
src/fetcher/openpgp.js Normal file
View file

@ -0,0 +1,131 @@
/*
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)
})
}

View file

@ -13,25 +13,35 @@ 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
* @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 {string} data.chat - Telegram public group name (slug)
* @param {string} data.user - Telegram 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>}
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
* @returns {Promise<object|string>} The fetched Telegram object
*/
export async function fn (data, opts) {
let timeoutHandle
@ -99,8 +109,7 @@ export async function fn (data, opts) {
})
})
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
clearTimeout(timeoutHandle)
return result
})
}

View file

@ -13,23 +13,40 @@ 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
const xmppStart = async (service, username, password) => {
/**
* 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) => {
return new Promise((resolve, reject) => {
const xmpp = client({
service,
username,
password
})
const xmpp = client({ ...params })
if (process.env.NODE_ENV !== 'production') {
debug(xmpp, true)
}
@ -47,17 +64,11 @@ const xmppStart = async (service, username, password) => {
/**
* Execute a fetch request
* @function
* @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>}
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
* @returns {Promise<Array<string>>} The fetched proofs from an XMPP account
*/
export async function fn (data, opts) {
try {
@ -69,11 +80,7 @@ export async function fn (data, opts) {
}
if (!xmpp || xmpp.status !== 'online') {
const xmppStartRes = await xmppStart(
opts.claims.xmpp.service,
opts.claims.xmpp.username,
opts.claims.xmpp.password
)
const xmppStartRes = await xmppStart(opts.claims.xmpp)
xmpp = xmppStartRes.xmpp
iqCaller = xmppStartRes.iqCaller
}
@ -176,8 +183,7 @@ export async function fn (data, opts) {
})()
})
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
clearTimeout(timeoutHandle)
return result
})
}

View file

@ -13,6 +13,11 @@ 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'
@ -26,4 +31,5 @@ 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'

View file

@ -32,16 +32,15 @@ import { Persona } from './persona.js'
* Fetch a public key using keyservers
* @function
* @param {string} identifier - Fingerprint or email address
* @param {string} [keyserverDomain=keys.openpgp.org] - Domain of the keyserver
* @returns {Promise<Profile>}
* @param {string} [keyserverDomain] - Domain of the keyserver
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
* @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) {
const keyserverBaseUrl = keyserverDomain
? `https://${keyserverDomain}`
: 'https://keys.openpgp.org'
export async function fetchHKP (identifier, keyserverDomain = 'keys.openpgp.org') {
const keyserverBaseUrl = `https://${keyserverDomain ?? 'keys.openpgp.org'}`
const hkp = new HKP(keyserverBaseUrl)
const lookupOpts = {
@ -76,7 +75,7 @@ export async function fetchHKP (identifier, keyserverDomain) {
* Fetch a public key using Web Key Directory
* @function
* @param {string} identifier - Identifier of format 'username@domain.tld`
* @returns {Promise<Profile>}
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
* @example
* const key = doip.keys.fetchWKD('alice@domain.tld');
*/
@ -115,7 +114,7 @@ export async function fetchWKD (identifier) {
* @function
* @param {string} username - Keybase username
* @param {string} fingerprint - Fingerprint of key
* @returns {Promise<Profile>}
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
* @example
* const key = doip.keys.fetchKeybase('alice', '123abc123abc');
*/
@ -155,10 +154,10 @@ export async function fetchKeybase (username, fingerprint) {
}
/**
* Get a public key from plaintext data
* Get a public key from armored public key text data
* @function
* @param {string} rawKeyContent - Plaintext ASCII-formatted public key data
* @returns {Promise<Profile>}
* @returns {Promise<Profile>} The profile from the armored public key
* @example
* const plainkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
*
@ -185,7 +184,7 @@ export async function fetchPlaintext (rawKeyContent) {
* Fetch a public key using an URI
* @function
* @param {string} uri - URI that defines the location of the key
* @returns {Promise<Profile>}
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
* @example
* const key1 = doip.keys.fetchURI('hkp:alice@domain.tld');
* const key2 = doip.keys.fetchURI('hkp:123abc123abc');
@ -231,7 +230,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>}
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
* @example
* const key1 = doip.keys.fetch('alice@domain.tld');
* const key2 = doip.keys.fetch('123abc123abc');
@ -273,7 +272,7 @@ export async function fetch (identifier) {
* Process a public key to get a profile
* @function
* @param {PublicKey} publicKey - The public key to parse
* @returns {Promise<Profile>}
* @returns {Promise<Profile>} The profile from the processed OpenPGP key
* @example
* const key = doip.keys.fetchURI('hkp:alice@domain.tld');
* const profile = doip.keys.parsePublicKey(key);
@ -292,6 +291,8 @@ export async function parsePublicKey (publicKey) {
const personas = []
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)

View file

@ -13,19 +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.
*/
import { Claim } from './claim.js'
/**
* A persona with identity claims
* @class
* @constructor
* @public
* @classdesc A persona with identity claims
* @example
* const claim = Claim('https://alice.tld', '123');
* const pers = Persona('Alice', 'About Alice', [claim]);
*/
export class Persona {
/**
* @param {string} name
* @param {import('./claim.js').Claim[]} claims
* @param {string} name - Name of the persona
* @param {Array<Claim>} claims - Claims of the persona
*/
constructor (name, claims) {
/**
@ -58,9 +58,15 @@ export class Persona {
* @public
*/
this.avatarUrl = null
/**
* Theme color
* @type {string | null}
* @public
*/
this.themeColor = null
/**
* List of identity claims
* @type {import('./claim.js').Claim[]}
* @type {Array<Claim>}
* @public
*/
this.claims = claims
@ -73,46 +79,84 @@ export class Persona {
}
/**
* Parse a JSON object and convert it into a persona
* @function
* @param {string} identifier
* @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
* @param {string} description - Description of the persona
*/
setDescription (description) {
this.description = description
}
/**
* Set the persona's email address
* @function
* @param {string} email
* @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
* @param {string} avatarUrl - URL to the persona's avatar
*/
setAvatarUrl (avatarUrl) {
this.avatarUrl = avatarUrl
}
/**
* Add a claim
* @function
* @param {import('./claim.js').Claim} claim
* @param {Claim} claim - Claim to add
*/
addClaim (claim) {
this.claims.push(claim)
}
/**
* Revoke the persona
* @function
*/
revoke () {
@ -120,9 +164,9 @@ export class Persona {
}
/**
* Get a JSON representation of the Profile object
* Get a JSON representation of the persona
* @function
* @returns {object}
* @returns {object} JSON representation of the persona
*/
toJSON () {
return {
@ -131,8 +175,29 @@ export class Persona {
email: this.email,
description: this.description,
avatarUrl: this.avatarUrl,
themeColor: this.themeColor,
isRevoked: this.isRevoked,
claims: this.claims.map(x => x.toJSON())
}
}
}
/**
* @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
}

View file

@ -13,13 +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.
*/
import { PublicKeyFetchMethod, PublicKeyEncoding, PublicKeyType } from './enums.js'
import { PublicKeyFetchMethod, PublicKeyEncoding, PublicKeyType, ProfileType } from './enums.js'
import { Persona } from './persona.js'
/**
* A profile of personas with identity claims
* @function
* @param {Array<import('./persona.js').Persona>} personas
* @public
* @class
* @classdesc A profile of personas with identity claims
* @param {Array<Persona>} personas - Personas of the profile
* @example
* const claim = Claim('https://alice.tld', '123');
* const pers = Persona('Alice', 'About Alice', [claim]);
@ -29,21 +29,16 @@ export class Profile {
/**
* Create a new profile
* @function
* @param {import('./enums.js').ProfileType} profileType
* @param {string} identifier
* @param {Array<import('./persona.js').Persona>} personas
* @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) {
/**
* Profile version
* @type {number}
* @public
*/
this.profileVersion = 2
/**
* Profile version
* @type {import('./enums.js').ProfileType}
* @type {ProfileType}
* @public
*/
this.profileType = profileType
@ -55,7 +50,7 @@ export class Profile {
this.identifier = identifier
/**
* List of personas
* @type {Array<import('./persona.js').Persona>}
* @type {Array<Persona>}
* @public
*/
this.personas = personas || []
@ -67,87 +62,74 @@ export class Profile {
this.primaryPersonaIndex = personas.length > 0 ? 0 : -1
/**
* The cryptographic key associated with the profile
* @property {object}
* @type {import('./types').ProfilePublicKey}
* @public
*/
this.publicKey = {
/**
* The type of cryptographic key
* @type {PublicKeyType}
* @public
*/
keyType: PublicKeyType.NONE,
/**
* The fingerprint of the cryptographic key
* @type {string | null}
* @public
*/
fingerprint: null,
/**
* The encoding of the cryptographic key
* @type {PublicKeyEncoding}
* @public
*/
encoding: PublicKeyEncoding.NONE,
/**
* The encoded cryptographic key
* @type {string | null}
* @public
*/
encodedKey: null,
/**
* The raw cryptographic key as object (to be removed during toJSON())
* @type {import('openpgp').PublicKey | import('jose').JWK | null}
* @public
*/
key: null,
/**
* Details on how to fetch the public key
* @property {object}
* @public
*/
fetch: {
/**
* The method to fetch the key
* @type {PublicKeyFetchMethod}
* @public
*/
method: PublicKeyFetchMethod.NONE,
/**
* The query to fetch the key
* @type {string | null}
* @public
*/
query: null,
/**
* The URL the method eventually resolved to
* @type {string | null}
* @public
*/
resolvedUrl: null
}
}
/**
* List of verifier URLs
* @type {{name: string, url: string}[]}
* @type {Array<import('./types').ProfileVerifier>}
* @public
*/
this.verifiers = []
}
/**
* Parse a JSON object and convert it into a profile
* @function
* @param {string} name
* @param {string} url
* @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 object
* Get a JSON representation of the profile
* @function
* @returns {object}
* @returns {object} JSON representation of the profile
*/
toJSON () {
return {
@ -171,3 +153,24 @@ export class Profile {
}
}
}
/**
* @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
}

View file

@ -14,9 +14,10 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
import { isNode } from 'browser-or-node'
import * as fetcher from './fetcher/index.js'
import { fetcher } from './index.js'
import { generateProxyURL } from './utils.js'
import { ProxyPolicy, ProofAccessRestriction } from './enums.js'
import { ServiceProvider } from './serviceProvider.js'
/**
* @module proofs
@ -28,10 +29,9 @@ import { ProxyPolicy, ProofAccessRestriction } from './enums.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.
* @async
* @param {import('./serviceProvider.js').ServiceProvider} data - Data from a claim definition
* @param {object} opts - Options to enable the request
* @returns {Promise<object|string>}
* @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
*/
export async function fetch (data, opts) {
if (isNode) {
@ -42,9 +42,9 @@ export async function fetch (data, opts) {
}
/**
* @param {import('./serviceProvider.js').ServiceProvider} data - Data from a claim definition
* @param {ServiceProvider} data - Data from a claim definition
* @param {object} opts - Options to enable the request
* @returns {Promise<object|string>}
* @returns {Promise<object|string>} Fetched proof data
*/
const handleBrowserRequests = (data, opts) => {
switch (opts.proxy.policy) {
@ -85,9 +85,9 @@ const handleBrowserRequests = (data, opts) => {
}
/**
* @param {import('./serviceProvider.js').ServiceProvider} data - Data from a claim definition
* @param {ServiceProvider} data - Data from a claim definition
* @param {object} opts - Options to enable the request
* @returns {Promise<object|string>}
* @returns {Promise<object|string>} Fetched proof data
*/
const handleNodeRequests = (data, opts) => {
switch (opts.proxy.policy) {
@ -106,9 +106,9 @@ const handleNodeRequests = (data, opts) => {
}
/**
* @param {import('./serviceProvider.js').ServiceProvider} data - Data from a claim definition
* @param {ServiceProvider} data - Data from a claim definition
* @param {object} opts - Options to enable the request
* @returns {Promise<object|string>}
* @returns {Promise<object|string>} Fetched proof data
*/
const createDefaultRequestPromise = (data, opts) => {
return new Promise((resolve, reject) => {
@ -132,9 +132,9 @@ const createDefaultRequestPromise = (data, opts) => {
}
/**
* @param {import('./serviceProvider.js').ServiceProvider} data - Data from a claim definition
* @param {ServiceProvider} data - Data from a claim definition
* @param {object} opts - Options to enable the request
* @returns {Promise<object|string>}
* @returns {Promise<object|string>} Fetched proof data
*/
const createProxyRequestPromise = (data, opts) => {
return new Promise((resolve, reject) => {
@ -171,9 +171,9 @@ const createProxyRequestPromise = (data, opts) => {
}
/**
* @param {import('./serviceProvider.js').ServiceProvider} data - Data from a claim definition
* @param {ServiceProvider} data - Data from a claim definition
* @param {object} opts - Options to enable the request
* @returns {Promise<object|string>}
* @returns {Promise<object|string>} Fetched proof data
*/
const createFallbackRequestPromise = (data, opts) => {
return new Promise((resolve, reject) => {

373
src/schemas.js Normal file
View file

@ -0,0 +1,373 @@
/*
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
}

View file

@ -13,129 +13,43 @@ 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
* @constructor
* @public
*/
export class ServiceProvider {
/**
* @param {object} spObj
* @param {import('./types').ServiceProviderObject} serviceProviderObject - JSON representation of a {@link ServiceProvider}
*/
constructor (spObj) {
constructor (serviceProviderObject) {
/**
* Details about the service provider
* @property {object}
* @type {import('./types').ServiceProviderAbout}
*/
this.about = {
this.about = serviceProviderObject.about
/**
* Identifier of the service provider (no whitespace or symbols, lowercase)
* @type {string}
* What the profile would look like if a claim matches this service provider
* @type {import('./types').ServiceProviderProfile}
*/
id: spObj.about.id,
this.profile = serviceProviderObject.profile
/**
* Full name of the service provider
* @type {string}
* Information about the claim matching process
* @type {import('./types').ServiceProviderClaim}
*/
name: spObj.about.name,
/**
* URL to the homepage of the service provider
* @type {string | null}
*/
homepage: spObj.about.homepage || null
}
/**
* What the profile would look like if the match is correct
* @property {object}
*/
this.profile = {
/**
* Profile name to be displayed
* @type {string}
*/
display: spObj.profile.display,
/**
* URI or URL for public access to the profile
* @type {string}
*/
uri: spObj.profile.uri,
/**
* URI or URL associated with the profile usually served as a QR code
* @type {string | null}
*/
qr: spObj.profile.qr || null
}
/**
* Details from the claim matching process
* @property {object}
*/
this.claim = {
/**
* Regular expression used to parse the URI
* @type {string}
*/
uriRegularExpression: spObj.claim.uriRegularExpression,
/**
* Whether this match automatically excludes other matches
* @type {boolean}
*/
uriIsAmbiguous: spObj.claim.uriIsAmbiguous
}
this.claim = serviceProviderObject.claim
/**
* Information for the proof verification process
* @property {object}
* @type {import('./types').ServiceProviderProof}
*/
this.proof = {
/**
* Details to request the potential proof
* @property {object}
*/
request: {
/**
* Location of the proof
* @type {string | null}
*/
uri: spObj.proof.request.uri,
/**
* Fetcher to be used to request the proof
* @type {string}
*/
fetcher: spObj.proof.request.fetcher,
/**
* Type of access restriction
* @type {import('./enums.js').ProofAccessRestriction}
*/
accessRestriction: spObj.proof.request.accessRestriction,
/**
* Data needed by the fetcher or proxy to request the proof
* @type {object}
*/
data: spObj.proof.request.data
},
/**
* Details about the expected response
* @property {object}
*/
response: {
/**
* Expected format of the proof
* @type {import('./enums.js').ProofFormat}
*/
format: spObj.proof.response.format
},
/**
* Details about the target located in the response
* @type {{format: import('./enums.js').ClaimFormat, encoding: import('./enums.js').EntityEncodingFormat, relation: import('./enums.js').ClaimRelation, path: string[]}[]}
*/
target: spObj.proof.target
}
this.proof = serviceProviderObject.proof
}
/**
* Get a JSON representation of the ServiceProvider object
* Get a JSON representation of the {@link ServiceProvider}
* @function
* @returns {object}
* @returns {import('./types').ServiceProviderObject} JSON representation of a {@link ServiceProvider}
*/
toJSON () {
return {

View file

@ -13,15 +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.
*/
/**
* 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
* @returns {ServiceProvider}
* @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({
@ -76,12 +85,155 @@ export function processURI (uri) {
}
export const functions = {
postprocess: (claimData, proofData) => {
claimData.profile.display = `@${proofData.result.preferredUsername}@${new URL(proofData.result.url).hostname}`
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',

View file

@ -0,0 +1,95 @@
/*
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
}
]

View file

@ -0,0 +1,127 @@
/*
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
}
]

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,8 +28,8 @@ export const reURI = /^https:\/\/(.*)\/u\/(.*)\/?/
/**
* @function
* @param {string} uri
* @returns {ServiceProvider}
* @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)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^dns:([a-zA-Z0-9.\-_]*)(?:\?(.*))?/
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,14 +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.
*/
/**
* 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
* @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)
@ -63,6 +73,14 @@ export function processURI (uri) {
})
}
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',

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/gist\.github\.com\/(.*)\/(.*)\/?/
/**
* @function
* @param {string} uri
* @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)
@ -53,12 +62,20 @@ export function processURI (uri) {
response: {
format: E.ProofFormat.JSON
},
target: [{
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']
}]
}
]
}
})
}

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/(.*)\/(.*)\/gitlab_proof\/?/
/**
* @function
* @param {string} uri
* @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)
@ -41,7 +50,6 @@ export function processURI (uri) {
uriIsAmbiguous: true
},
proof: {
uri,
request: {
fetcher: E.Fetcher.HTTP,
accessRestriction: E.ProofAccessRestriction.NONE,

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/news\.ycombinator\.com\/user\?id=(.*)\/?/
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,6 +13,8 @@ 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'
@ -25,7 +27,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'
@ -35,8 +37,13 @@ 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,
@ -49,7 +56,7 @@ const _data = {
hackernews,
lobsters,
forem,
// forgejo,
forgejo,
gitea,
gitlab,
github,
@ -58,7 +65,10 @@ const _data = {
owncast,
stackexchange,
keybase,
opencollective
opencollective,
orcid,
pronounscc,
discord
}
export const list = Object.keys(_data)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^irc:\/\/(.*)\/([a-zA-Z0-9\-[\]\\`_^{|}]*)/
/**
* @function
* @param {string} uri
* @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)
@ -31,7 +40,7 @@ export function processURI (uri) {
name: 'IRC'
},
profile: {
display: `irc://${match[1]}/${match[2]}`,
display: `${match[1]}/${match[2]}`,
uri,
qr: null
},

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,15 +28,17 @@ export const reURI = /^https:\/\/keybase.io\/(.*)\/?/
/**
* @function
* @param {string} uri
* @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: 'web',
name: 'keybase'
id: 'keybase',
name: 'keybase',
homepage: 'https://keybase.io'
},
profile: {
display: match[1],

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/liberapay\.com\/(.*)\/?/
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,15 +28,17 @@ export const reURI = /^https:\/\/lichess\.org\/@\/(.*)\/?/
/**
* @function
* @param {string} uri
* @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: 'web',
name: 'lichess'
id: 'lichess',
name: 'Lichess',
homepage: 'https://lichess.org'
},
profile: {
display: match[1],

View file

@ -13,14 +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.
*/
/**
* 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\/(.*)\/?/
export const reURI = /^https:\/\/lobste\.rs\/(?:~|u\/)(.*)\/?/
/**
* @function
* @param {string} uri
* @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)
@ -33,7 +42,7 @@ export function processURI (uri) {
},
profile: {
display: match[1],
uri,
uri: `https://lobste.rs/~${match[1]}`,
qr: null
},
claim: {
@ -42,11 +51,11 @@ export function processURI (uri) {
},
proof: {
request: {
uri: `https://lobste.rs/u/${match[1]}.json`,
uri: `https://lobste.rs/~${match[1]}.json`,
fetcher: E.Fetcher.HTTP,
accessRestriction: E.ProofAccessRestriction.NOCORS,
data: {
url: `https://lobste.rs/u/${match[1]}.json`,
url: `https://lobste.rs/~${match[1]}.json`,
format: E.ProofFormat.JSON
}
},
@ -64,6 +73,10 @@ export function processURI (uri) {
}
export const tests = [
{
uri: 'https://lobste.rs/~Alice',
shouldMatch: true
},
{
uri: 'https://lobste.rs/u/Alice',
shouldMatch: true
@ -72,6 +85,10 @@ export const tests = [
uri: 'https://lobste.rs/u/Alice/',
shouldMatch: true
},
{
uri: 'https://domain.org/~Alice',
shouldMatch: false
},
{
uri: 'https://domain.org/u/Alice',
shouldMatch: false

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^matrix:u\/(?:@)?([^@:]*:[^?]*)(\?.*)?/
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/opencollective\.com\/(.*)\/?/
/**
* @function
* @param {string} uri
* @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)
@ -47,7 +56,7 @@ export function processURI (uri) {
accessRestriction: E.ProofAccessRestriction.NOCORS,
data: {
url: 'https://api.opencollective.com/graphql/v2',
query: `{ "query": "query { collective(slug: \\"${match[1]}\\") { longDescription } }" }`
query: `{ "query": "query { account(slug: \\"${match[1]}\\") { longDescription } }" }`
}
},
response: {
@ -57,7 +66,7 @@ export function processURI (uri) {
format: E.ClaimFormat.URI,
encoding: E.EntityEncodingFormat.PLAIN,
relation: E.ClaimRelation.CONTAINS,
path: ['data', 'collective', 'longDescription']
path: ['data', 'account', 'longDescription']
}]
}
})

View file

@ -0,0 +1,179 @@
/*
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
}
]

View file

@ -0,0 +1,98 @@
/*
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
}
]

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/(.*)/
/**
* @function
* @param {string} uri
* @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)

View file

@ -0,0 +1,100 @@
/*
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
}
]

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/(?:www\.)?reddit\.com\/user\/(.*)\/comments\/(.
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -21,7 +29,8 @@ const reStackExchange = /\.stackexchange$/
/**
* @function
* @param {string} uri
* @param {string} uri - Claim URI to process
* @returns {ServiceProvider} The service provider information based on the claim URI
*/
export function processURI (uri) {
const [, domain, id] = uri.match(reURI)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /https:\/\/t.me\/([A-Za-z0-9_]{5,32})\?proof=([A-Za-z0-9_]{
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^https:\/\/twitter\.com\/(.*)\/status\/([0-9]*)(?:\?.*)?/
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,6 +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.
*/
/**
* 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'
@ -20,7 +28,8 @@ export const reURI = /^xmpp:([a-zA-Z0-9.\-_]*)@([a-zA-Z0-9.\-_]*)(?:\?(.*))?/
/**
* @function
* @param {string} uri
* @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)

View file

@ -13,7 +13,7 @@ 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 { readCleartextMessage, verify } from 'openpgp'
import { CleartextMessage, PublicKey, readCleartextMessage, verify } from 'openpgp'
import { Claim } from './claim.js'
import { fetchURI } from './openpgp.js'
import { Profile } from './profile.js'
@ -26,12 +26,11 @@ import { Persona } from './persona.js'
/**
* Extract the profile from a signature and fetch the associated key
* @async
* @param {string} signature - The plaintext signature to parse
* @returns {Promise<import('./profile.js').Profile>}
* @returns {Promise<Profile>} The profile obtained from the signature
*/
export async function parse (signature) {
/** @type {import('openpgp').CleartextMessage} */
/** @type {CleartextMessage} */
let sigData
// Read the signature
@ -84,7 +83,7 @@ export async function parse (signature) {
if (sigKeys.length > 0) {
try {
obtainedKey.query = sigKeys[0]
/** @type {import('openpgp').PublicKey} */
/** @type {PublicKey} */
obtainedKey.data = (await fetchURI(obtainedKey.query)).publicKey.key
obtainedKey.method = obtainedKey.query.split(':')[0]
} catch (e) {}

198
src/types.js Normal file
View file

@ -0,0 +1,198 @@
/*
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 = {}

View file

@ -24,11 +24,8 @@ 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 {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}
* @param {import('./types').VerificationConfig} opts - Options to enable the request
* @returns {string} Generated proxy URL
*/
export function generateProxyURL (type, data, opts) {
try {
@ -43,7 +40,7 @@ export function generateProxyURL (type, data, opts) {
queryStrings.push(`${key}=${encodeURIComponent(data[key])}`)
})
const scheme = opts.proxy.scheme ? opts.proxy.scheme : 'https'
const scheme = opts.proxy.scheme ?? 'https'
return `${scheme}://${opts.proxy.hostname}/api/3/get/${type}?${queryStrings.join(
'&'
@ -53,8 +50,8 @@ export function generateProxyURL (type, data, opts) {
/**
* Generate the string that must be found in the proof to verify a 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}
* @param {ClaimFormat} format - The claim's format
* @returns {string} Generate claim
*/
export function generateClaim (fingerprint, format) {
switch (format) {
@ -73,7 +70,7 @@ 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>}
* @returns {Array<string>} List of URIs extracted from input
*/
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

View file

@ -17,6 +17,7 @@ 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
@ -24,14 +25,11 @@ import { decodeHTML, decodeXML } from 'entities'
*/
/**
* Check if string contains the proof
* @function
* @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>}
* @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
*/
const containsProof = async (data, params) => {
const fingerprintFormatted = generateClaim(params.target, params.claimFormat)
@ -84,6 +82,27 @@ const containsProof = async (data, params) => {
case '2a':
case '2b':
case '2y':
try {
// Patch until promise.race properly works on WASM
if (parseInt(match[0].split('$')[2]) > 12) continue
const hashPromise = bcryptVerify({
password: fingerprintURI.toLowerCase(),
hash: match[0]
})
.then(result => result)
.catch(_ => false)
result = await Promise.race([hashPromise, timeoutPromise]).then((result) => {
clearTimeout(timeoutHandle)
return result
})
} 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
@ -102,12 +121,31 @@ const containsProof = async (data, params) => {
} catch (err) {
result = false
}
}
break
case 'argon2':
case 'argon2i':
case 'argon2d':
case 'argon2id':
try {
const hashPromise = argon2Verify({
password: fingerprintURI.toLowerCase(),
hash: match[0]
})
.then(result => result)
.catch(_ => false)
result = await Promise.race([hashPromise, timeoutPromise]).then((result) => {
clearTimeout(timeoutHandle)
return result
})
} catch (err) {
result = false
}
// Accept mixed-case fingerprints until deadline
if (!result) {
try {
const hashPromise = argon2Verify({
password: fingerprintURI,
@ -123,6 +161,7 @@ const containsProof = async (data, params) => {
} catch (err) {
result = false
}
}
break
default:
@ -174,15 +213,12 @@ const containsProof = async (data, params) => {
}
/**
* Run a JSON object through the verification process
* @function
* @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>}
* @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
*/
const runJSON = async (proofData, checkPath, params) => {
if (!proofData) {
@ -229,14 +265,14 @@ const runJSON = async (proofData, checkPath, params) => {
}
/**
* Run the verification by finding the formatted fingerprint in the proof
* @async
* Run the verification by searching for the proof in the fetched data
* @param {object} proofData - The proof data
* @param {import('./serviceProvider.js').ServiceProvider} claimData - The claim data
* @param {ServiceProvider} claimData - The claim data
* @param {string} fingerprint - The fingerprint
* @returns {Promise<object>}
* @returns {Promise<import('./types').VerificationResult>} Result of the verification
*/
export async function run (proofData, claimData, fingerprint) {
/** @type {import('./types').VerificationResult} */
const res = {
result: false,
completed: false,

View file

@ -129,9 +129,9 @@ describe('openpgp.fetchURI', () => {
})
describe('openpgp.fetchHKP', () => {
it('should be a function (2 arguments)', () => {
it('should be a function (1 required argument, 1 optional argument)', () => {
expect(openpgp.fetchHKP).to.be.a('function')
expect(openpgp.fetchHKP).to.have.length(2)
expect(openpgp.fetchHKP).to.have.length(1)
})
it('should return a Key object when provided a valid fingerprint', async () => {
expect(await openpgp.fetchHKP(pubKeyFingerprint)).to.be.instanceOf(

View file

@ -50,6 +50,8 @@ 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 () => {
@ -63,6 +65,8 @@ 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)
@ -71,6 +75,8 @@ 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)

10887
yarn.lock

File diff suppressed because it is too large Load diff