forked from Mirrors/doipjs
Compare commits
197 commits
matrix-roo
...
dev
Author | SHA1 | Date | |
---|---|---|---|
0f43a7b9e3 | |||
2153185729 | |||
a523ec4191 | |||
|
fa57e7b538 | ||
|
758255f652 | ||
|
fff5ce4aca | ||
06ea6732de | |||
|
041e22c52d | ||
|
6d464176df | ||
|
9b1a5d4d26 | ||
|
689117ac98 | ||
|
6d2606c8a9 | ||
|
fecaf7df12 | ||
|
c52632cbb6 | ||
|
f8d0422443 | ||
|
fe6588dcbb | ||
|
deaa858345 | ||
|
336029fd87 | ||
|
3f8579513a | ||
|
2ef792fbbb | ||
|
edc3f401bc | ||
|
7115ecbfe6 | ||
|
d672ea2328 | ||
|
d7d9556165 | ||
|
6b8fd0b099 | ||
|
7fe9d29690 | ||
|
0dd0d91104 | ||
|
626077f880 | ||
|
7639962521 | ||
|
a9c1ffabfe | ||
|
c02742e041 | ||
|
70ee790f0a | ||
|
4a77591c57 | ||
|
cd96131ad1 | ||
|
dd2c134a75 | ||
|
7eaaedc12a | ||
|
468f150509 | ||
|
462348dced | ||
|
f724e81c06 | ||
|
9bdc0a639f | ||
|
3c4f61ada1 | ||
|
2687742e23 | ||
|
056ebd6d83 | ||
|
34ad2d718d | ||
|
a616dcd66d | ||
|
8cd40ea422 | ||
|
8a01cdf9fa | ||
|
a3c9e9137d | ||
|
2dd18395dd | ||
|
fc0af3dd1d | ||
|
34688bb644 | ||
|
c5d6c2c9d9 | ||
|
0d1e400d02 | ||
|
7e5d15ac0f | ||
|
c1cb3fcbb6 | ||
|
a25e94002c | ||
|
bd864b796d | ||
|
ea8eb234ad | ||
|
264645b381 | ||
|
ba6941448c | ||
|
beb78e8227 | ||
|
9c9b387fc9 | ||
|
cb397e42b7 | ||
|
4aba882b1b | ||
|
1baffe9c92 | ||
|
feda782136 | ||
|
81d5ba0d57 | ||
|
cae1020f45 | ||
|
c2f9efa698 | ||
|
549a86c121 | ||
|
d8529a7f92 | ||
|
2a314bef52 | ||
|
8ea07938e4 | ||
|
cb841fe9b7 | ||
|
09b052c7b9 | ||
|
0dfb0bc38a | ||
|
027d7e1a4b | ||
|
75ec28b618 | ||
|
89ab8cccc1 | ||
|
b4ac82b010 | ||
|
a8a97b2d85 | ||
|
c3f7df2113 | ||
|
bc5fe110a7 | ||
|
77bfb03ea6 | ||
|
0c0d9a9ec3 | ||
|
e6228ed22e | ||
|
65752d0dde | ||
|
128c9bf682 | ||
|
decda24d26 | ||
|
6eb2435127 | ||
|
58561d6e0d | ||
|
15dca5a771 | ||
|
0ba5de77e7 | ||
|
0546cc1e49 | ||
|
00d646e2b2 | ||
|
149ac6f71e | ||
|
a30339272a | ||
|
fd8c760689 | ||
|
b674f113c7 | ||
|
92d150efea | ||
|
535d35bd48 | ||
|
0f7c444d3c | ||
|
7f1d972fa7 | ||
|
f3f4c96f9b | ||
|
9f31bc5349 | ||
|
067c35a82c | ||
|
73ae4b296f | ||
|
06f1cdbe51 | ||
|
e2d34723e5 | ||
|
be37a25352 | ||
|
0166a30e3c | ||
|
f7c90edd7d | ||
|
bfe5a6f486 | ||
|
0e543946db | ||
|
542bab3232 | ||
|
4f5e5592f4 | ||
|
fceb5b6f9b | ||
|
53e73afa19 | ||
|
fb0aaa3e17 | ||
|
473916dc33 | ||
|
3bcb724b7f | ||
|
0e29471d89 | ||
|
dc0806e738 | ||
|
46cffbf056 | ||
|
1b1549bcc7 | ||
|
d069569b32 | ||
|
28a3cb0e9a | ||
|
231d93348d | ||
|
a6ff593d94 | ||
|
d6c31ef50c | ||
|
15bbad0e6b | ||
|
7d65a21c1d | ||
|
166d8d5cf3 | ||
|
650c389ae2 | ||
|
d943021579 | ||
|
955bbd8a08 | ||
|
92ce86a6e0 | ||
|
322b2c4529 | ||
|
e85d77045c | ||
|
e98995ec0d | ||
|
e2a344848c | ||
|
976869aa9c | ||
|
0d9b91cf62 | ||
|
4db27c4876 | ||
|
025cd12aba | ||
|
3d643afdfa | ||
|
e84f09be5d | ||
|
525e876ad1 | ||
|
a017433ca6 | ||
|
06b7d24cce | ||
|
f3ce2accd4 | ||
|
f4d26cc15f | ||
|
f5ea8fd549 | ||
|
eb72827887 | ||
|
1ffc8b4175 | ||
|
4385fa1e51 | ||
|
05fa92063a | ||
|
1720c59093 | ||
|
bfc64ce898 | ||
|
421f907206 | ||
|
f46b32528d | ||
|
2efa2cbde8 | ||
|
c7bd4fe81e | ||
|
e9fdeb0bf8 | ||
|
56b2722ce0 | ||
|
a3f14228a5 | ||
|
82ee7e2dda | ||
|
1e37969dc6 | ||
|
643dabff9f | ||
|
4ff11947e4 | ||
|
66e3e68e2f | ||
|
13e20fe6fb | ||
|
e6b824bcf8 | ||
|
ca6aec04dd | ||
|
a0c1bdaa90 | ||
|
fcc62a6025 | ||
|
6d89cc0542 | ||
|
2c722d31a9 | ||
|
f64f9cff58 | ||
|
cabaa4ec16 | ||
|
2d6b02eded | ||
|
a46311f1f8 | ||
|
516896c631 | ||
|
38242bd1e6 | ||
|
37fe998fd8 | ||
|
3c1302c150 | ||
|
6a7bc8b5b2 | ||
|
802f8011dc | ||
|
e0502c78bb | ||
|
2a29832110 | ||
|
1710195211 | ||
|
007282ef22 | ||
|
c992b4fe28 | ||
|
8b83dbd8ed | ||
|
220a70be61 | ||
|
e28315e87f | ||
|
8a1f8ad586 |
121 changed files with 101277 additions and 25667 deletions
101
.drone.yml
101
.drone.yml
|
@ -1,101 +0,0 @@
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: test
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: run tests
|
|
||||||
image: node
|
|
||||||
commands:
|
|
||||||
- yarn
|
|
||||||
- yarn run prepare
|
|
||||||
- yarn run test
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- push
|
|
||||||
- pull_request
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: publish-npm
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: prepare
|
|
||||||
image: node
|
|
||||||
commands:
|
|
||||||
- yarn
|
|
||||||
- yarn run prepare
|
|
||||||
- name: publish on npm
|
|
||||||
image: plugins/npm
|
|
||||||
settings:
|
|
||||||
username: yarmo_eu
|
|
||||||
password:
|
|
||||||
from_secret: npm_token
|
|
||||||
email:
|
|
||||||
from_secret: npm_email
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: publish-docker-latest
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: publish latest proxy container
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
dockerfile: docker/proxy/Dockerfile
|
|
||||||
repo: keyoxide/doip-proxy
|
|
||||||
tags: latest
|
|
||||||
- name: build tag proxy container
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
dockerfile: docker/proxy/Dockerfile
|
|
||||||
repo: keyoxide/doip-proxy
|
|
||||||
auto_tag: true
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
event:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
---
|
|
||||||
kind: pipeline
|
|
||||||
name: publish-docker-dev
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: build dev proxy container
|
|
||||||
image: plugins/docker
|
|
||||||
settings:
|
|
||||||
username:
|
|
||||||
from_secret: docker_username
|
|
||||||
password:
|
|
||||||
from_secret: docker_password
|
|
||||||
dockerfile: docker/proxy/Dockerfile
|
|
||||||
repo: keyoxide/doip-proxy
|
|
||||||
tags: dev
|
|
||||||
|
|
||||||
depends_on:
|
|
||||||
- test
|
|
||||||
|
|
||||||
trigger:
|
|
||||||
branch:
|
|
||||||
- main
|
|
||||||
event:
|
|
||||||
- push
|
|
22
.eslintrc.json
Normal file
22
.eslintrc.json
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2021": true,
|
||||||
|
"node": true
|
||||||
|
},
|
||||||
|
"extends": [
|
||||||
|
"standard",
|
||||||
|
"plugin:jsdoc/recommended"
|
||||||
|
],
|
||||||
|
"overrides": [
|
||||||
|
],
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": "latest",
|
||||||
|
"sourceType": "module"
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
},
|
||||||
|
"plugins": [
|
||||||
|
"jsdoc"
|
||||||
|
]
|
||||||
|
}
|
16
.gitea/issue_template/bug.md
Normal file
16
.gitea/issue_template/bug.md
Normal 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
|
||||||
|
|
22
.gitea/issue_template/claim_verification_bug.md
Normal file
22
.gitea/issue_template/claim_verification_bug.md
Normal 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
|
||||||
|
|
||||||
|
|
38
.gitea/issue_template/new_claim.md
Normal file
38
.gitea/issue_template/new_claim.md
Normal 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)
|
|
@ -1,5 +1,4 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
yarn run license:check
|
|
||||||
yarn test
|
yarn test
|
|
@ -12,11 +12,11 @@ ignore
|
||||||
docs
|
docs
|
||||||
examples
|
examples
|
||||||
\.husky
|
\.husky
|
||||||
|
\.woodpecker
|
||||||
|
|
||||||
package.json
|
package.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
rollup.config.js
|
||||||
\.editorconfig
|
\.editorconfig
|
||||||
\.gitignore
|
\.gitignore
|
||||||
\.licenseignore
|
\.licenseignore
|
||||||
\.drone.yml
|
|
||||||
Dockerfile
|
|
24
.woodpecker/.publish-npm.yml
Normal file
24
.woodpecker/.publish-npm.yml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: tag
|
||||||
|
steps:
|
||||||
|
prepare:
|
||||||
|
image: node
|
||||||
|
commands:
|
||||||
|
- yarn --pure-lockfile
|
||||||
|
- yarn run prepare
|
||||||
|
|
||||||
|
publish-npm:
|
||||||
|
when:
|
||||||
|
branch: main
|
||||||
|
event: tag
|
||||||
|
image: plugins/npm
|
||||||
|
settings:
|
||||||
|
username: yarmo_eu
|
||||||
|
token:
|
||||||
|
from_secret: npm_token
|
||||||
|
email:
|
||||||
|
from_secret: npm_email
|
||||||
|
|
||||||
|
depends_on:
|
||||||
|
- test
|
7
.woodpecker/.test.yml
Normal file
7
.woodpecker/.test.yml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
steps:
|
||||||
|
test:
|
||||||
|
image: node
|
||||||
|
commands:
|
||||||
|
- yarn --pure-lockfile
|
||||||
|
- yarn run prepare
|
||||||
|
- yarn run test
|
BIN
.yarn/install-state.gz
Normal file
BIN
.yarn/install-state.gz
Normal file
Binary file not shown.
6
.yarnrc.yml
Normal file
6
.yarnrc.yml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
nodeLinker: node-modules
|
||||||
|
npmScopes:
|
||||||
|
myriation:
|
||||||
|
npmPublishRegistry: https://git.myriation.xyz/api/packages/myriation/npm/
|
||||||
|
npmAlwaysAuth: true
|
||||||
|
npmAuthToken: REPLACE-ME
|
162
CHANGELOG.md
162
CHANGELOG.md
|
@ -6,6 +6,168 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
||||||
|
### Changed
|
||||||
|
- Made HTTP scheme for proxy calls configurable
|
||||||
|
- Replaced standard with eslint
|
||||||
|
### Fixed
|
||||||
|
- Sort OpenPGP certifications by chronological order
|
||||||
|
- Allowing white space in fingerprint
|
||||||
|
- Use correct format for displaying ActivityPub claims
|
||||||
|
- Missing JSDOC types
|
||||||
|
- JS bundling
|
||||||
|
|
||||||
|
## [0.18.3] - 2023-03-27
|
||||||
|
### Added
|
||||||
|
- OpenCollective claim verification
|
||||||
|
- Keybase claim verification
|
||||||
|
- GraphQL fetcher protocol
|
||||||
|
- HTML entity decoding in proofs
|
||||||
|
### Changed
|
||||||
|
- Replace devto with forem
|
||||||
|
### Fixed
|
||||||
|
- forem service provider ambiguity
|
||||||
|
|
||||||
|
## [0.18.2] - 2023-03-08
|
||||||
|
### Changed
|
||||||
|
- Use oembed for Twitter verification
|
||||||
|
### Removed
|
||||||
|
- query-string dependency
|
||||||
|
### Fixed
|
||||||
|
- Matrix URI format
|
||||||
|
|
||||||
|
## [0.18.1] - 2022-12-12
|
||||||
|
### Changed
|
||||||
|
- Improved XMPP proof requests
|
||||||
|
### Fixed
|
||||||
|
- Added missing user-agent headers
|
||||||
|
### Removed
|
||||||
|
- jsdom dependency
|
||||||
|
|
||||||
|
## [0.18.0] - 2022-11-17
|
||||||
|
### Changed
|
||||||
|
- Allow ActivityPub verification through posts
|
||||||
|
- Improve type consistency
|
||||||
|
### Removed
|
||||||
|
- Proxy server code
|
||||||
|
|
||||||
|
## [0.17.5] - 2022-11-14
|
||||||
|
### Fixed
|
||||||
|
- Implementation of postprocess function
|
||||||
|
|
||||||
|
## [0.17.3] - 2022-11-14
|
||||||
|
### Changed
|
||||||
|
- Add fetcher to lib exports
|
||||||
|
|
||||||
|
## [0.17.2] - 2022-10-27
|
||||||
|
### Fixed
|
||||||
|
- Proxy verification options
|
||||||
|
|
||||||
|
## [0.17.1] - 2022-10-25
|
||||||
|
### Fixed
|
||||||
|
- ActivityPub definition
|
||||||
|
- Claim verification logic
|
||||||
|
|
||||||
|
## [0.17.0] - 2022-10-24
|
||||||
|
### Added
|
||||||
|
- ActivityPub claim verification
|
||||||
|
|
||||||
|
## [0.16.4] - 2022-10-07
|
||||||
|
### Fixed
|
||||||
|
- superuser.com not being detected
|
||||||
|
|
||||||
|
## [0.16.3] - 2022-09-30
|
||||||
|
### Changed
|
||||||
|
- Updated dependencies
|
||||||
|
|
||||||
## [0.16.2] - 2022-09-21
|
## [0.16.2] - 2022-09-21
|
||||||
### Added
|
### Added
|
||||||
- Support for hashed proofs
|
- Support for hashed proofs
|
||||||
|
|
58
README.md
58
README.md
|
@ -1,22 +1,18 @@
|
||||||
# doip.js
|
# 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)
|
![](static/doip.png)
|
||||||
![](doip.png)
|
![](doip.png)
|
||||||
|
|
||||||
doip.js allows websites and Node.js projects to verify decentralized online
|
[doip.js](https://codeberg.org/keyoxide/doipjs) allows websites and Node.js projects to verify decentralized online
|
||||||
identities based on OpenPGP.
|
identities.
|
||||||
|
|
||||||
Source code available at [codeberg.org](https://codeberg.org/keyoxide/doipjs).
|
|
||||||
|
|
||||||
Documentation available at [js.doip.rocks](https://js.doip.rocks).
|
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)
|
## Installation (node)
|
||||||
|
|
||||||
Install using **yarn** or **npm**:
|
Install using **yarn** or **npm**:
|
||||||
|
@ -56,32 +52,32 @@ const verifyIdentity = async (url, fp) => {
|
||||||
verifyIdentity('dns:doip.rocks', '9f0048ac0b23301e1f77e994909f6bd6f80f485d')
|
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.
|
bidirectionally linked to Yarmo's cryptographic key.
|
||||||
|
|
||||||
## About Keyoxide
|
## Contributing
|
||||||
|
|
||||||
[Keyoxide](https://keyoxide.org/), made by Yarmo Mackenbach, is a modern, secure
|
Anyone can contribute!
|
||||||
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.
|
|
||||||
|
|
||||||
## Community
|
Developers are invited to:
|
||||||
|
|
||||||
There's a [Keyoxide Matrix room](https://matrix.to/#/#keyoxide:matrix.org) where
|
- fork the repository and play around
|
||||||
we discuss everything DOIP and Keyoxide.
|
- 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
|
Everyone is invited to:
|
||||||
project is a step in the right direction for the internet.
|
|
||||||
|
|
||||||
## 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
|
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.
|
||||||
[NLnet](https://nlnet.nl/project/Keyoxide/).
|
|
||||||
|
## 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.
|
||||||
|
|
14597
dist/doip.core.js
vendored
Normal file
14597
dist/doip.core.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
dist/doip.core.min.js
vendored
Normal file
6
dist/doip.core.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
36961
dist/doip.fetchers.js
vendored
Normal file
36961
dist/doip.fetchers.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21
dist/doip.fetchers.min.js
vendored
Normal file
21
dist/doip.fetchers.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
36958
dist/doip.fetchers.minimal.js
vendored
Normal file
36958
dist/doip.fetchers.minimal.js
vendored
Normal file
File diff suppressed because one or more lines are too long
21
dist/doip.fetchers.minimal.min.js
vendored
Normal file
21
dist/doip.fetchers.minimal.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
16408
dist/doip.js
vendored
16408
dist/doip.js
vendored
File diff suppressed because one or more lines are too long
21
dist/doip.min.js
vendored
21
dist/doip.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,6 +0,0 @@
|
||||||
FROM node:16-alpine
|
|
||||||
WORKDIR /app
|
|
||||||
COPY . .
|
|
||||||
RUN yarn --production --pure-lockfile
|
|
||||||
EXPOSE 3000
|
|
||||||
CMD yarn run proxy
|
|
|
@ -1,23 +0,0 @@
|
||||||
const doip = require('../src')
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
// Fetch the key using HKP
|
|
||||||
const key = await doip.keys.fetchHKP("test@doip.rocks")
|
|
||||||
|
|
||||||
// Process it to extract the UIDs and their claims
|
|
||||||
const obj = await doip.keys.process(key)
|
|
||||||
|
|
||||||
// Process every claim for every user
|
|
||||||
obj.users.forEach(async user => {
|
|
||||||
user.claims.forEach(async claim => {
|
|
||||||
// Match the claim
|
|
||||||
await claim.match()
|
|
||||||
|
|
||||||
// Verify the claim
|
|
||||||
await claim.verify()
|
|
||||||
console.log(claim)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
|
@ -1,14 +0,0 @@
|
||||||
const doip = require('../src')
|
|
||||||
|
|
||||||
const main = async () => {
|
|
||||||
// Fetch the key using WKD
|
|
||||||
const key = await doip.keys.fetchWKD("test@doip.rocks")
|
|
||||||
|
|
||||||
// Process it to extract the UIDs and their claims
|
|
||||||
const obj = await doip.keys.process(key)
|
|
||||||
|
|
||||||
// Log the claims of the first UID
|
|
||||||
console.log(obj.users[0].claims)
|
|
||||||
}
|
|
||||||
|
|
||||||
main()
|
|
18
examples/fetch-profile-aspe.js
Normal file
18
examples/fetch-profile-aspe.js
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import * as doip from '../src/index.js'
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
// Fetch the profile using ASPE
|
||||||
|
const profile = await doip.asp.fetchASPE("aspe:keyoxide.org:6WJK26YKF6WUVPIZTS2I2BIT64")
|
||||||
|
|
||||||
|
// Process every claim for every persona
|
||||||
|
profile.personas[0].claims.forEach(async claim => {
|
||||||
|
// Match the claim
|
||||||
|
claim.match()
|
||||||
|
|
||||||
|
// Verify the claim
|
||||||
|
await claim.verify()
|
||||||
|
console.log(claim)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
20
examples/fetch-profile-hkp.js
Normal file
20
examples/fetch-profile-hkp.js
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
import * as doip from '../src/index.js'
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
// Fetch the profile using HKP
|
||||||
|
const profile = await doip.openpgp.fetchHKP("test@doip.rocks")
|
||||||
|
|
||||||
|
// Process every claim for every persona
|
||||||
|
profile.personas.forEach(async persona => {
|
||||||
|
persona.claims.forEach(async claim => {
|
||||||
|
// Match the claim
|
||||||
|
await claim.match()
|
||||||
|
|
||||||
|
// Verify the claim
|
||||||
|
await claim.verify()
|
||||||
|
console.log(claim)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
|
@ -1,4 +1,4 @@
|
||||||
const doip = require('../src')
|
import * as doip from '../src/index.js'
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
// Obtain the plaintext public key
|
// Obtain the plaintext public key
|
||||||
|
@ -28,14 +28,11 @@ fCRSXrr7SZxIu7I8jfQrxc0k9XhpPI/gdlgRqoEG2lMyqFaWzyoI9dyoVwji78rg
|
||||||
=Csr+
|
=Csr+
|
||||||
-----END PGP PUBLIC KEY BLOCK-----`
|
-----END PGP PUBLIC KEY BLOCK-----`
|
||||||
|
|
||||||
// Fetch the key using WKD
|
// Use the plaintext key to get a profile
|
||||||
const key = await doip.keys.fetchPlaintext(pubKeyPlaintext)
|
const profile = await doip.openpgp.fetchPlaintext(pubKeyPlaintext)
|
||||||
|
|
||||||
// Process it to extract the UIDs and their claims
|
|
||||||
const obj = await doip.keys.process(key)
|
|
||||||
|
|
||||||
// Log the claims of the first UID
|
// Log the claims of the first UID
|
||||||
console.log(obj.users[0].claims)
|
console.log(profile.personas[0].claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
11
examples/fetch-profile-wkd.js
Normal file
11
examples/fetch-profile-wkd.js
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import * as doip from '../src/index.js'
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
// Fetch the profile using WKD
|
||||||
|
const profile = await doip.openpgp.fetchWKD("test@doip.rocks")
|
||||||
|
|
||||||
|
// Log the claims of the first persona
|
||||||
|
console.log(profile.personas[0].claims)
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
|
@ -1,4 +1,4 @@
|
||||||
const doip = require('../src')
|
import * as doip from '../src/index.js'
|
||||||
|
|
||||||
const signature = `-----BEGIN PGP SIGNED MESSAGE-----
|
const signature = `-----BEGIN PGP SIGNED MESSAGE-----
|
||||||
Hash: SHA512
|
Hash: SHA512
|
||||||
|
@ -25,10 +25,10 @@ cXbjvHSGniZ7M3S9S8knAfIquPvTp7+L7wWgSSB5VObPp1r+96n87hyFZUp7PCvl
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
// Process the OpenPGP signature
|
// Process the OpenPGP signature
|
||||||
const sigProfile = await doip.signatures.process(signature)
|
const profile = await doip.signatures.process(signature)
|
||||||
|
|
||||||
// Log the processed signature profile
|
// Log the claims of the first persona
|
||||||
console.log(sigProfile.users[0].claims)
|
console.log(profile.users[0].claims)
|
||||||
}
|
}
|
||||||
|
|
||||||
main()
|
main()
|
9
examples/test-service-provider.js
Normal file
9
examples/test-service-provider.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import * as doip from '../src/index.js'
|
||||||
|
|
||||||
|
const main = async () => {
|
||||||
|
// const sp = doip.ServiceProviderDefinitions.data['activitypub'].processURI('https://fosstodon.org/@yarmo')
|
||||||
|
const sp = doip.ServiceProviderDefinitions.data['discourse'].processURI('https://domain.org/u/alice')
|
||||||
|
console.log(sp);
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
|
@ -1,4 +1,4 @@
|
||||||
const doip = require('../src')
|
import * as doip from '../src/index.js'
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
// Generate the claim
|
// Generate the claim
|
||||||
|
|
10
jsconfig.json
Normal file
10
jsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "ES2020",
|
||||||
|
"target": "ES2020",
|
||||||
|
"checkJs": true,
|
||||||
|
"moduleResolution": "node"
|
||||||
|
},
|
||||||
|
"include": ["src", "examples"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"plugins": ["plugins/markdown"],
|
"plugins": [
|
||||||
|
"plugins/markdown",
|
||||||
|
"node_modules/jsdoc-tsimport-plugin"
|
||||||
|
],
|
||||||
"source": {
|
"source": {
|
||||||
"include": ["./src", "./README.md"]
|
"include": ["./src", "./README.md"]
|
||||||
},
|
},
|
||||||
|
@ -12,21 +15,26 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"opts": {
|
"opts": {
|
||||||
"template": "node_modules/clean-jsdoc-theme",
|
"template": "node_modules/docdash",
|
||||||
"theme_opts": {
|
"destination": "docs/"
|
||||||
"theme": "light",
|
},
|
||||||
"menu": [
|
"docdash": {
|
||||||
{
|
"collapse": true,
|
||||||
"title": "Source code",
|
"meta": {
|
||||||
"link": "https://codeberg.org/keyoxide/doipjs",
|
"title": "doipjs",
|
||||||
"target": "_blank"
|
"description": "Documentation for the doip.js library"
|
||||||
},
|
},
|
||||||
{
|
"menu": {
|
||||||
"title": "Keyoxide",
|
"Keyoxide": {
|
||||||
"link": "https://keyoxide.org",
|
"href":"https://keyoxide.org",
|
||||||
"target": "_blank"
|
"target":"_blank",
|
||||||
}
|
"class":"menu-item"
|
||||||
]
|
},
|
||||||
|
"Keyoxide docs": {
|
||||||
|
"href":"https://docs.keyoxide.org",
|
||||||
|
"target":"_blank",
|
||||||
|
"class":"menu-item"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
96
package.json
96
package.json
|
@ -1,63 +1,79 @@
|
||||||
{
|
{
|
||||||
"name": "doipjs",
|
"name": "@myriation/doipjs",
|
||||||
"version": "0.16.2",
|
"version": "1.2.9+myriaiton.1",
|
||||||
"description": "Decentralized OpenPGP Identity Proofs library in Node.js",
|
"description": "Decentralized Online Identity Proofs library in Node.js",
|
||||||
|
"type": "module",
|
||||||
"main": "./src/index.js",
|
"main": "./src/index.js",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"default": "./src/index.js"
|
||||||
|
},
|
||||||
|
"./fetchers": {
|
||||||
|
"default": "./src/fetcher/index.js"
|
||||||
|
},
|
||||||
|
"./fetchers-minimal": {
|
||||||
|
"default": "./src/fetcher/index.minimal.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@4.3.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openpgp/hkp-client": "^0.0.2",
|
"@openpgp/hkp-client": "^0.0.3",
|
||||||
"@openpgp/wkd-client": "^0.0.3",
|
"@openpgp/wkd-client": "^0.0.4",
|
||||||
"@xmpp/client": "^0.13.1",
|
"@xmpp/client": "^0.13.1",
|
||||||
"@xmpp/debug": "^0.13.0",
|
"@xmpp/debug": "^0.13.0",
|
||||||
"axios": "^0.25.0",
|
"axios": "^1.6.5",
|
||||||
"browser-or-node": "^1.3.0",
|
"browser-or-node": "^1.3.0",
|
||||||
"cors": "^2.8.5",
|
"entities": "^4.4.0",
|
||||||
"dotenv": "^8.2.0",
|
|
||||||
"express": "^4.17.1",
|
|
||||||
"express-validator": "^6.10.0",
|
|
||||||
"hash-wasm": "^4.9.0",
|
"hash-wasm": "^4.9.0",
|
||||||
"irc-upd": "^0.11.0",
|
"irc-upd": "^0.11.0",
|
||||||
"jsdom": "^20.0.0",
|
"jose": "^4.14.4",
|
||||||
"merge-options": "^3.0.3",
|
"merge-options": "^3.0.3",
|
||||||
"openpgp": "^5.0",
|
"openpgp": "^5.5.0",
|
||||||
"query-string": "^6.14.1",
|
"rfc4648": "^1.5.2",
|
||||||
"valid-url": "^1.0.9",
|
"valid-url": "^1.0.9",
|
||||||
"validator": "^13.5.2"
|
"validator": "^13.9.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"browserify": "^17.0.0",
|
"@rollup/plugin-commonjs": "^25.0.2",
|
||||||
"browserify-shim": "^3.8.14",
|
"@rollup/plugin-json": "^6.0.0",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.1.0",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"chai-as-promised": "^7.1.1",
|
"chai-as-promised": "^7.1.1",
|
||||||
"chai-match-pattern": "^1.2.0",
|
"docdash": "^2.0.2",
|
||||||
"clean-jsdoc-theme": "^3.2.4",
|
"eslint": "^8.39.0",
|
||||||
|
"eslint-config-standard": "^17.0.0",
|
||||||
|
"eslint-plugin-import": "^2.27.5",
|
||||||
|
"eslint-plugin-jsdoc": "^48.0.4",
|
||||||
|
"eslint-plugin-n": "^15.7.0",
|
||||||
|
"eslint-plugin-promise": "^6.1.1",
|
||||||
"husky": "^7.0.0",
|
"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",
|
"license-check-and-add": "^4.0.3",
|
||||||
"lint-staged": "^11.0.0",
|
"lint-staged": "^11.0.0",
|
||||||
"minify": "^9.1",
|
"minify": "^9.1",
|
||||||
"mocha": "^9.2.0",
|
"mocha": "^9.2.0",
|
||||||
"nodemon": "^2.0.19",
|
"rollup": "^3.26.2",
|
||||||
"standard": "^16.0.3"
|
"rollup-plugin-polyfill-node": "^0.12.0",
|
||||||
|
"rollup-plugin-visualizer": "^5.9.2"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"release": "yarn run test && yarn run release:bundle && yarn run release:minify",
|
"release": "node ./prerelease.js && yarn run test && yarn run build",
|
||||||
"release:bundle": "./node_modules/.bin/browserify ./src/index.js --standalone doip -x openpgp -x jsdom -x @xmpp/client -x @xmpp/debug -x irc-upd -o ./dist/doip.js",
|
"build": "rm -rf ./dist/ && yarn run build:bundle && yarn run build:minify",
|
||||||
"release:minify": "./node_modules/.bin/minify ./dist/doip.js > ./dist/doip.min.js",
|
"build:bundle": "rollup -c",
|
||||||
"license:check": "./node_modules/.bin/license-check-and-add check",
|
"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",
|
||||||
"license:add": "./node_modules/.bin/license-check-and-add add",
|
"license:check": "license-check-and-add check",
|
||||||
"license:remove": "./node_modules/.bin/license-check-and-add remove",
|
"license:add": "license-check-and-add add",
|
||||||
"docs:lib": "./node_modules/.bin/jsdoc -c jsdoc-lib.json -r -d ./docs -P package.json",
|
"license:remove": "license-check-and-add remove",
|
||||||
"standard:check": "./node_modules/.bin/standard ./src",
|
"docs:lib": "jsdoc -c jsdoc-lib.json -r -d ./docs -P package.json",
|
||||||
"standard:fix": "./node_modules/.bin/standard --fix ./src",
|
"lint": "eslint ./src",
|
||||||
"mocha": "./node_modules/.bin/mocha",
|
"lint:fix": "eslint ./src --fix",
|
||||||
"test": "yarn run standard:check && yarn run license:check && yarn run mocha",
|
"test": "yarn lint && yarn run license:check && yarn run mocha",
|
||||||
"proxy": "NODE_ENV=production node ./src/proxy/",
|
|
||||||
"proxy:dev": "NODE_ENV=development ./node_modules/.bin/nodemon ./src/proxy/",
|
|
||||||
"prepare": "husky install"
|
"prepare": "husky install"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://codeberg.org/keyoxide/doipjs"
|
"url": "https://git.myriation.org/myriation/doipjs"
|
||||||
},
|
},
|
||||||
"homepage": "https://js.doip.rocks",
|
"homepage": "https://js.doip.rocks",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
@ -69,13 +85,5 @@
|
||||||
"identity"
|
"identity"
|
||||||
],
|
],
|
||||||
"author": "Yarmo Mackenbach <yarmo@yarmo.eu> (https://yarmo.eu)",
|
"author": "Yarmo Mackenbach <yarmo@yarmo.eu> (https://yarmo.eu)",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0"
|
||||||
"browserify": {
|
|
||||||
"transform": [
|
|
||||||
"browserify-shim"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"browserify-shim": {
|
|
||||||
"openpgp": "global:openpgp"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
33
prerelease.js
Normal file
33
prerelease.js
Normal 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()
|
73
rollup.config.js
Normal file
73
rollup.config.js
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
import commonjs from '@rollup/plugin-commonjs'
|
||||||
|
import json from '@rollup/plugin-json'
|
||||||
|
import { nodeResolve } from '@rollup/plugin-node-resolve'
|
||||||
|
import nodePolyfills from 'rollup-plugin-polyfill-node'
|
||||||
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
const fetchersExternalId = fileURLToPath(
|
||||||
|
new URL(
|
||||||
|
'src/fetcher/index.js',
|
||||||
|
import.meta.url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
input: 'src/index.js',
|
||||||
|
output: {
|
||||||
|
file: './dist/doip.core.js',
|
||||||
|
format: 'iife',
|
||||||
|
name: 'doip',
|
||||||
|
globals: {
|
||||||
|
'openpgp': 'openpgp',
|
||||||
|
[fetchersExternalId]: 'doipFetchers'
|
||||||
|
},
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
include: './src/**'
|
||||||
|
},
|
||||||
|
external: ['openpgp', './fetcher/index.js'],
|
||||||
|
plugins: [
|
||||||
|
commonjs(),
|
||||||
|
json(),
|
||||||
|
nodeResolve({
|
||||||
|
browser: true
|
||||||
|
}),
|
||||||
|
nodePolyfills()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'src/fetcher/index.js',
|
||||||
|
output: {
|
||||||
|
file: './dist/doip.fetchers.js',
|
||||||
|
format: 'iife',
|
||||||
|
name: 'doipFetchers'
|
||||||
|
},
|
||||||
|
external: [],
|
||||||
|
plugins: [
|
||||||
|
commonjs(),
|
||||||
|
json(),
|
||||||
|
nodeResolve({
|
||||||
|
browser: true
|
||||||
|
}),
|
||||||
|
nodePolyfills()
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'src/fetcher/index.minimal.js',
|
||||||
|
output: {
|
||||||
|
file: './dist/doip.fetchers.minimal.js',
|
||||||
|
format: 'iife',
|
||||||
|
name: 'doipFetchers'
|
||||||
|
},
|
||||||
|
external: [],
|
||||||
|
plugins: [
|
||||||
|
commonjs(),
|
||||||
|
json(),
|
||||||
|
nodeResolve({
|
||||||
|
browser: true
|
||||||
|
}),
|
||||||
|
nodePolyfills()
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
183
src/asp.js
Normal file
183
src/asp.js
Normal file
|
@ -0,0 +1,183 @@
|
||||||
|
/*
|
||||||
|
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 axios from 'axios'
|
||||||
|
import { decodeProtectedHeader, importJWK, compactVerify, calculateJwkThumbprint } from 'jose'
|
||||||
|
import { base32, base64url } from 'rfc4648'
|
||||||
|
import { Claim } from './claim.js'
|
||||||
|
import { Persona } from './persona.js'
|
||||||
|
import { Profile } from './profile.js'
|
||||||
|
import { ProfileType, PublicKeyEncoding, PublicKeyFetchMethod, PublicKeyType } from './enums.js'
|
||||||
|
|
||||||
|
const SupportedCryptoAlg = ['EdDSA', 'ES256', 'ES256K', 'ES384', 'ES512']
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Functions related to Ariadne Signature Profiles
|
||||||
|
* @module asp
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch a public key using Web Key Directory
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - ASPE URI
|
||||||
|
* @returns {Promise<Profile>} The fetched profile
|
||||||
|
* @example
|
||||||
|
* const key = await doip.aspe.fetchASPE('aspe:domain.example:1234567890');
|
||||||
|
*/
|
||||||
|
export async function fetchASPE (uri) {
|
||||||
|
const re = /aspe:(.*):(.*)/
|
||||||
|
|
||||||
|
if (!re.test(uri)) {
|
||||||
|
throw new Error('Invalid ASPE URI')
|
||||||
|
}
|
||||||
|
|
||||||
|
const matches = uri.match(re)
|
||||||
|
const domainPart = matches[1]
|
||||||
|
const localPart = matches[2].toUpperCase()
|
||||||
|
|
||||||
|
const profileUrl = `https://${domainPart}/.well-known/aspe/id/${localPart}`
|
||||||
|
let profileJws
|
||||||
|
|
||||||
|
try {
|
||||||
|
profileJws = await axios.get(
|
||||||
|
profileUrl,
|
||||||
|
{
|
||||||
|
responseType: 'text'
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then((/** @type {import('axios').AxiosResponse} */ response) => {
|
||||||
|
if (response.status === 200) {
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((/** @type {import('axios').AxiosResponse} */ response) => response.data)
|
||||||
|
} catch (e) {
|
||||||
|
throw new Error(`Error fetching Keybase key: ${e.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const profile = await parseProfileJws(profileJws, uri)
|
||||||
|
profile.publicKey.fetch.method = PublicKeyFetchMethod.ASPE
|
||||||
|
profile.publicKey.fetch.query = uri
|
||||||
|
profile.publicKey.fetch.resolvedUrl = profileUrl
|
||||||
|
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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>} The extracted profile
|
||||||
|
* @example
|
||||||
|
* const key = await doip.aspe.parseProfileJws('...', 'aspe:domain.example:123');
|
||||||
|
*/
|
||||||
|
export async function parseProfileJws (profileJws, uri) {
|
||||||
|
const matches = uri.match(/aspe:(.*):(.*)/)
|
||||||
|
const localPart = matches[2].toUpperCase()
|
||||||
|
|
||||||
|
// Decode the headers
|
||||||
|
const protectedHeader = decodeProtectedHeader(profileJws)
|
||||||
|
|
||||||
|
// Extract the JWK
|
||||||
|
if (!SupportedCryptoAlg.includes(protectedHeader.alg)) {
|
||||||
|
throw new Error('Invalid profile JWS: wrong key algorithm')
|
||||||
|
}
|
||||||
|
if (!protectedHeader.kid) {
|
||||||
|
throw new Error('Invalid profile JWS: missing key identifier')
|
||||||
|
}
|
||||||
|
if (!protectedHeader.jwk) {
|
||||||
|
throw new Error('Invalid profile JWS: missing key')
|
||||||
|
}
|
||||||
|
const publicKey = await importJWK(protectedHeader.jwk, protectedHeader.alg)
|
||||||
|
|
||||||
|
// Compute and verify the fingerprint
|
||||||
|
const fp = await computeJwkFingerprint(protectedHeader.jwk)
|
||||||
|
|
||||||
|
if (fp !== protectedHeader.kid) {
|
||||||
|
throw new Error('Invalid profile JWS: wrong key')
|
||||||
|
}
|
||||||
|
if (localPart && fp !== localPart) {
|
||||||
|
throw new Error('Invalid profile JWS: wrong key')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decode the payload
|
||||||
|
const { payload } = await compactVerify(profileJws, publicKey)
|
||||||
|
const payloadJson = JSON.parse(new TextDecoder().decode(payload))
|
||||||
|
|
||||||
|
// Verify the payload
|
||||||
|
if (!(Object.prototype.hasOwnProperty.call(payloadJson, 'http://ariadne.id/type') && payloadJson['http://ariadne.id/type'] === 'profile')) {
|
||||||
|
throw new Error('Invalid profile JWS: JWS is not a profile')
|
||||||
|
}
|
||||||
|
if (!(Object.prototype.hasOwnProperty.call(payloadJson, 'http://ariadne.id/version') && payloadJson['http://ariadne.id/version'] === 0)) {
|
||||||
|
throw new Error('Invalid profile JWS: profile version not supported')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract data from the payload
|
||||||
|
/** @type {string} */
|
||||||
|
const profileName = payloadJson['http://ariadne.id/name']
|
||||||
|
/** @type {string} */
|
||||||
|
const profileDescription = payloadJson['http://ariadne.id/description']
|
||||||
|
/** @type {string} */
|
||||||
|
const profileThemeColor = payloadJson['http://ariadne.id/color']
|
||||||
|
/** @type {Array<string>} */
|
||||||
|
const profileClaims = payloadJson['http://ariadne.id/claims']
|
||||||
|
|
||||||
|
const profileClaimsParsed = profileClaims.map(x => new Claim(x, uri))
|
||||||
|
|
||||||
|
const pe = new Persona(profileName, profileClaimsParsed)
|
||||||
|
if (profileDescription) {
|
||||||
|
pe.setDescription(profileDescription)
|
||||||
|
}
|
||||||
|
if (profileThemeColor && /^#([0-9A-F]{3}){1,2}$/i.test(profileThemeColor)) {
|
||||||
|
pe.themeColor = profileThemeColor
|
||||||
|
}
|
||||||
|
|
||||||
|
const profile = new Profile(ProfileType.ASP, uri, [pe])
|
||||||
|
profile.publicKey.fingerprint = fp
|
||||||
|
profile.publicKey.encoding = PublicKeyEncoding.JWK
|
||||||
|
profile.publicKey.encodedKey = JSON.stringify(protectedHeader.jwk)
|
||||||
|
profile.publicKey.key = protectedHeader.jwk
|
||||||
|
|
||||||
|
switch (protectedHeader.alg) {
|
||||||
|
case 'ES256':
|
||||||
|
profile.publicKey.keyType = PublicKeyType.ES256
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'EdDSA':
|
||||||
|
profile.publicKey.keyType = PublicKeyType.EDDSA
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
profile.publicKey.keyType = PublicKeyType.UNKNOWN
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 - 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')
|
||||||
|
const fingerprintBytes = base64url.parse(thumbprint, { loose: true }).slice(0, 16)
|
||||||
|
const fingerprint = base32.stringify(fingerprintBytes, { pad: false })
|
||||||
|
|
||||||
|
return fingerprint
|
||||||
|
}
|
327
src/claim.js
327
src/claim.js
|
@ -13,74 +13,105 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const validator = require('validator')
|
import isAlphanumeric from 'validator/lib/isAlphanumeric.js'
|
||||||
const validUrl = require('valid-url')
|
import { isUri } from 'valid-url'
|
||||||
const mergeOptions = require('merge-options')
|
import mergeOptions from 'merge-options'
|
||||||
const proofs = require('./proofs')
|
import { fetch } from './proofs.js'
|
||||||
const verifications = require('./verifications')
|
import { run } from './verifications.js'
|
||||||
const claimDefinitions = require('./claimDefinitions')
|
import { list, data as _data } from './serviceProviders/index.js'
|
||||||
const defaults = require('./defaults')
|
import { opts as _opts } from './defaults.js'
|
||||||
const E = require('./enums')
|
import { ClaimStatus } from './enums.js'
|
||||||
|
import { ServiceProvider } from './serviceProvider.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class
|
* @class
|
||||||
* @classdesc OpenPGP-based identity claim
|
* @classdesc Identity claim
|
||||||
* @property {string} uri - The claim's URI
|
* @property {string} uri - The claim's URI
|
||||||
* @property {string} fingerprint - The fingerprint to verify the claim against
|
* @property {string} fingerprint - The fingerprint to verify the claim against
|
||||||
* @property {string} status - The current status of the claim
|
* @property {number} status - The current status code of the claim
|
||||||
* @property {Array<object>} matches - The claim definitions matched against the URI
|
* @property {Array<object>} matches - The claim definitions matched against the URI
|
||||||
* @property {object} verification - The result of the verification process
|
* @example
|
||||||
|
* const claim = doip.Claim();
|
||||||
|
* const claim = doip.Claim('dns:domain.tld?type=TXT');
|
||||||
|
* const claim = doip.Claim('dns:domain.tld?type=TXT', '123abc123abc');
|
||||||
*/
|
*/
|
||||||
class Claim {
|
export class Claim {
|
||||||
/**
|
/**
|
||||||
* Initialize a Claim object
|
* Initialize a Claim object
|
||||||
* @constructor
|
* @param {string} [uri] - The URI of the identity claim
|
||||||
* @param {string|object} [uri] - The URI of the identity claim or a JSONified Claim instance
|
|
||||||
* @param {string} [fingerprint] - The fingerprint of the OpenPGP key
|
* @param {string} [fingerprint] - The fingerprint of the OpenPGP key
|
||||||
* @example
|
|
||||||
* const claim = doip.Claim();
|
|
||||||
* const claim = doip.Claim('dns:domain.tld?type=TXT');
|
|
||||||
* const claim = doip.Claim('dns:domain.tld?type=TXT', '123abc123abc');
|
|
||||||
* const claimAlt = doip.Claim(JSON.stringify(claim));
|
|
||||||
*/
|
*/
|
||||||
constructor (uri, fingerprint) {
|
constructor (uri, fingerprint) {
|
||||||
// Import JSON
|
|
||||||
if (typeof uri === 'object' && 'claimVersion' in uri) {
|
|
||||||
const data = uri
|
|
||||||
switch (data.claimVersion) {
|
|
||||||
case 1:
|
|
||||||
this._uri = data.uri
|
|
||||||
this._fingerprint = data.fingerprint
|
|
||||||
this._status = data.status
|
|
||||||
this._matches = data.matches
|
|
||||||
this._verification = data.verification
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
throw new Error('Invalid claim version')
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify validity of URI
|
// Verify validity of URI
|
||||||
if (uri && !validUrl.isUri(uri)) {
|
if (uri && !isUri(uri)) {
|
||||||
throw new Error('Invalid URI')
|
throw new Error('Invalid URI')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify validity of fingerprint
|
// Verify validity of fingerprint
|
||||||
if (fingerprint) {
|
if (fingerprint) {
|
||||||
try {
|
try {
|
||||||
validator.isAlphanumeric(fingerprint)
|
// @ts-ignore
|
||||||
|
isAlphanumeric.default(fingerprint)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error('Invalid fingerprint')
|
throw new Error('Invalid fingerprint')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._uri = uri || null
|
/**
|
||||||
this._fingerprint = fingerprint || null
|
* @type {string}
|
||||||
this._status = E.ClaimStatus.INIT
|
*/
|
||||||
this._matches = null
|
this._uri = uri || ''
|
||||||
this._verification = null
|
/**
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
this._fingerprint = fingerprint || ''
|
||||||
|
/**
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this._status = ClaimStatus.INIT
|
||||||
|
/**
|
||||||
|
* @type {Array<ServiceProvider>}
|
||||||
|
*/
|
||||||
|
this._matches = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {*} claimObject - JSON representation of a claim
|
||||||
|
* @returns {Claim} Parsed claim
|
||||||
|
* @throws Will throw an error if the JSON object can't be coerced into a Claim
|
||||||
|
* @example
|
||||||
|
* doip.Claim.fromJSON(JSON.stringify(claim));
|
||||||
|
*/
|
||||||
|
static fromJSON (claimObject) {
|
||||||
|
/** @type {Claim} */
|
||||||
|
let claim
|
||||||
|
let result
|
||||||
|
|
||||||
|
if (typeof claimObject === 'object' && 'claimVersion' in claimObject) {
|
||||||
|
switch (claimObject.claimVersion) {
|
||||||
|
case 1:
|
||||||
|
result = importJsonClaimVersion1(claimObject)
|
||||||
|
if (result instanceof Error) {
|
||||||
|
throw result
|
||||||
|
}
|
||||||
|
claim = result
|
||||||
|
break
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
result = importJsonClaimVersion2(claimObject)
|
||||||
|
if (result instanceof Error) {
|
||||||
|
throw result
|
||||||
|
}
|
||||||
|
claim = result
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid claim version')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return claim
|
||||||
}
|
}
|
||||||
|
|
||||||
get uri () {
|
get uri () {
|
||||||
|
@ -96,27 +127,20 @@ class Claim {
|
||||||
}
|
}
|
||||||
|
|
||||||
get matches () {
|
get matches () {
|
||||||
if (this._status === E.ClaimStatus.INIT) {
|
if (this._status === ClaimStatus.INIT) {
|
||||||
throw new Error('This claim has not yet been matched')
|
throw new Error('This claim has not yet been matched')
|
||||||
}
|
}
|
||||||
return this._matches
|
return this._matches
|
||||||
}
|
}
|
||||||
|
|
||||||
get verification () {
|
|
||||||
if (this._status !== E.ClaimStatus.VERIFIED) {
|
|
||||||
throw new Error('This claim has not yet been verified')
|
|
||||||
}
|
|
||||||
return this._verification
|
|
||||||
}
|
|
||||||
|
|
||||||
set uri (uri) {
|
set uri (uri) {
|
||||||
if (this._status !== E.ClaimStatus.INIT) {
|
if (this._status !== ClaimStatus.INIT) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Cannot change the URI, this claim has already been matched'
|
'Cannot change the URI, this claim has already been matched'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Verify validity of URI
|
// Verify validity of URI
|
||||||
if (uri && !validUrl.isUri(uri)) {
|
if (uri.length > 0 && !isUri(uri)) {
|
||||||
throw new Error('The URI was invalid')
|
throw new Error('The URI was invalid')
|
||||||
}
|
}
|
||||||
// Remove leading and trailing spaces
|
// Remove leading and trailing spaces
|
||||||
|
@ -126,7 +150,7 @@ class Claim {
|
||||||
}
|
}
|
||||||
|
|
||||||
set fingerprint (fingerprint) {
|
set fingerprint (fingerprint) {
|
||||||
if (this._status === E.ClaimStatus.VERIFIED) {
|
if (this._status === ClaimStatus.VERIFIED) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Cannot change the fingerprint, this claim has already been verified'
|
'Cannot change the fingerprint, this claim has already been verified'
|
||||||
)
|
)
|
||||||
|
@ -142,26 +166,22 @@ class Claim {
|
||||||
throw new Error("Cannot change a claim's matches")
|
throw new Error("Cannot change a claim's matches")
|
||||||
}
|
}
|
||||||
|
|
||||||
set verification (anything) {
|
|
||||||
throw new Error("Cannot change a claim's verification result")
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Match the claim's URI to candidate definitions
|
* Match the claim's URI to candidate definitions
|
||||||
* @function
|
* @function
|
||||||
*/
|
*/
|
||||||
match () {
|
match () {
|
||||||
if (this._status !== E.ClaimStatus.INIT) {
|
if (this._status !== ClaimStatus.INIT) {
|
||||||
throw new Error('This claim was already matched')
|
throw new Error('This claim was already matched')
|
||||||
}
|
}
|
||||||
if (this._uri === null) {
|
if (this._uri.length === 0 || !isUri(this._uri)) {
|
||||||
throw new Error('This claim has no URI')
|
throw new Error('This claim has no URI')
|
||||||
}
|
}
|
||||||
|
|
||||||
this._matches = []
|
this._matches = []
|
||||||
|
|
||||||
claimDefinitions.list.every((name, i) => {
|
list.every((name, i) => {
|
||||||
const def = claimDefinitions.data[name]
|
const def = _data[name]
|
||||||
|
|
||||||
// If the candidate is invalid, continue matching
|
// If the candidate is invalid, continue matching
|
||||||
if (!def.reURI.test(this._uri)) {
|
if (!def.reURI.test(this._uri)) {
|
||||||
|
@ -169,7 +189,12 @@ class Claim {
|
||||||
}
|
}
|
||||||
|
|
||||||
const candidate = def.processURI(this._uri)
|
const candidate = def.processURI(this._uri)
|
||||||
if (candidate.match.isAmbiguous) {
|
// If the candidate could not be processed, continue matching
|
||||||
|
if (!candidate) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (candidate.claim.uriIsAmbiguous) {
|
||||||
// Add to the possible candidates
|
// Add to the possible candidates
|
||||||
this._matches.push(candidate)
|
this._matches.push(candidate)
|
||||||
} else {
|
} else {
|
||||||
|
@ -182,7 +207,7 @@ class Claim {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
||||||
this._status = E.ClaimStatus.MATCHED
|
this._status = this._matches.length === 0 ? ClaimStatus.NO_MATCHES : ClaimStatus.MATCHED
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -190,51 +215,49 @@ class Claim {
|
||||||
* checked for the fingerprint. The verification stops when either a positive
|
* checked for the fingerprint. The verification stops when either a positive
|
||||||
* result was obtained, or an unambiguous claim definition was processed
|
* result was obtained, or an unambiguous claim definition was processed
|
||||||
* regardless of the result.
|
* regardless of the result.
|
||||||
* @async
|
|
||||||
* @function
|
* @function
|
||||||
* @param {object} [opts] - Options for proxy, fetchers
|
* @param {import('./types').VerificationConfig} [opts] - Options for proxy, fetchers
|
||||||
*/
|
*/
|
||||||
async verify (opts) {
|
async verify (opts) {
|
||||||
if (this._status === E.ClaimStatus.INIT) {
|
if (this._status === ClaimStatus.INIT) {
|
||||||
throw new Error('This claim has not yet been matched')
|
throw new Error('This claim has not yet been matched')
|
||||||
}
|
}
|
||||||
if (this._status === E.ClaimStatus.VERIFIED) {
|
if (this._status >= 200) {
|
||||||
throw new Error('This claim has already been verified')
|
throw new Error('This claim has already been verified')
|
||||||
}
|
}
|
||||||
if (this._fingerprint === null) {
|
if (this._fingerprint.length === 0) {
|
||||||
throw new Error('This claim has no fingerprint')
|
throw new Error('This claim has no fingerprint')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle options
|
// Handle options
|
||||||
opts = mergeOptions(defaults.opts, opts || {})
|
opts = mergeOptions(_opts, opts || {})
|
||||||
|
|
||||||
// If there are no matches
|
// If there are no matches
|
||||||
if (this._matches.length === 0) {
|
if (this._matches.length === 0) {
|
||||||
this._verification = {
|
this.status = ClaimStatus.NO_MATCHES
|
||||||
result: false,
|
|
||||||
completed: true,
|
|
||||||
proof: {},
|
|
||||||
errors: ['No matches for claim']
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For each match
|
// For each match
|
||||||
for (let index = 0; index < this._matches.length; index++) {
|
for (let index = 0; index < this._matches.length; index++) {
|
||||||
const claimData = this._matches[index]
|
// Continue if a result was already obtained
|
||||||
|
if (this._status >= 200) { continue }
|
||||||
|
|
||||||
|
let claimData = this._matches[index]
|
||||||
|
|
||||||
|
/** @type {import('./types').VerificationResult | null} */
|
||||||
let verificationResult = null
|
let verificationResult = null
|
||||||
let proofData = null
|
let proofData = null
|
||||||
let proofFetchError
|
let proofFetchError
|
||||||
|
|
||||||
try {
|
try {
|
||||||
proofData = await proofs.fetch(claimData, opts)
|
proofData = await fetch(claimData, opts)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
proofFetchError = err
|
proofFetchError = err
|
||||||
}
|
}
|
||||||
|
|
||||||
if (proofData) {
|
if (proofData) {
|
||||||
// Run the verification process
|
// Run the verification process
|
||||||
verificationResult = await verifications.run(
|
verificationResult = await run(
|
||||||
proofData.result,
|
proofData.result,
|
||||||
claimData,
|
claimData,
|
||||||
this._fingerprint
|
this._fingerprint
|
||||||
|
@ -243,40 +266,43 @@ class Claim {
|
||||||
fetcher: proofData.fetcher,
|
fetcher: proofData.fetcher,
|
||||||
viaProxy: proofData.viaProxy
|
viaProxy: proofData.viaProxy
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate the result
|
||||||
|
const def = _data[claimData.about.id]
|
||||||
|
if (def.functions?.validate && verificationResult.completed && verificationResult.result) {
|
||||||
|
try {
|
||||||
|
(verificationResult.result = await def.functions.validate(claimData, proofData, verificationResult, opts))
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post process the data
|
||||||
|
if (def.functions?.postprocess) {
|
||||||
|
try {
|
||||||
|
({ claimData, proofData } = await def.functions.postprocess(claimData, proofData, opts))
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Consider the proof completed but with a negative result
|
// Consider the proof completed but with a negative result
|
||||||
verificationResult = verificationResult || {
|
verificationResult = verificationResult || {
|
||||||
result: false,
|
result: false,
|
||||||
completed: true,
|
completed: true,
|
||||||
proof: {},
|
proof: null,
|
||||||
errors: [proofFetchError]
|
errors: [proofFetchError]
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isAmbiguous()) {
|
|
||||||
// Assume a wrong match and continue
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (verificationResult.completed) {
|
if (this.isAmbiguous() && !verificationResult.result) {
|
||||||
// Store the result, keep a single match and stop verifying
|
// Assume a wrong match and continue
|
||||||
this._verification = verificationResult
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (verificationResult.result) {
|
||||||
|
this._status = verificationResult.proof.viaProxy ? ClaimStatus.VERIFIED_VIA_PROXY : ClaimStatus.VERIFIED
|
||||||
this._matches = [claimData]
|
this._matches = [claimData]
|
||||||
index = this._matches.length
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fail safe verification result
|
this._status = this._status >= 200 ? this._status : ClaimStatus.NO_PROOF_FOUND
|
||||||
this._verification = this._verification
|
|
||||||
? this._verification
|
|
||||||
: {
|
|
||||||
result: false,
|
|
||||||
completed: true,
|
|
||||||
proof: {},
|
|
||||||
errors: ['Unknown error']
|
|
||||||
}
|
|
||||||
|
|
||||||
this._status = E.ClaimStatus.VERIFIED
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -284,34 +310,115 @@ class Claim {
|
||||||
* of the candidates is unambiguous. An ambiguous claim should never be
|
* of the candidates is unambiguous. An ambiguous claim should never be
|
||||||
* displayed in an user interface when its result is negative.
|
* displayed in an user interface when its result is negative.
|
||||||
* @function
|
* @function
|
||||||
* @returns {boolean}
|
* @returns {boolean} Whether the claim is ambiguous
|
||||||
*/
|
*/
|
||||||
isAmbiguous () {
|
isAmbiguous () {
|
||||||
if (this._status === E.ClaimStatus.INIT) {
|
if (this._status < ClaimStatus.MATCHED) {
|
||||||
throw new Error('The claim has not been matched yet')
|
throw new Error('The claim has not been matched yet')
|
||||||
}
|
}
|
||||||
if (this._matches.length === 0) {
|
if (this._matches.length === 0) {
|
||||||
throw new Error('The claim has no matches')
|
throw new Error('The claim has no matches')
|
||||||
}
|
}
|
||||||
return this._matches.length > 1 || this._matches[0].match.isAmbiguous
|
if (this._status >= 200 && this._status < 300) return false
|
||||||
|
return this._matches.length > 1 || this._matches[0].claim.uriIsAmbiguous
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a JSON representation of the Claim object. Useful when transferring
|
* Get a JSON representation of the Claim object. Useful when transferring
|
||||||
* data between instances/machines.
|
* data between instances/machines.
|
||||||
* @function
|
* @function
|
||||||
* @returns {object}
|
* @returns {object} JSON reprentation of the claim
|
||||||
*/
|
*/
|
||||||
toJSON () {
|
toJSON () {
|
||||||
|
let displayProfileName = this._uri
|
||||||
|
let displayProfileUrl = null
|
||||||
|
let displayProofUrl = null
|
||||||
|
let displayServiceProviderName = null
|
||||||
|
let displayServiceProviderId = null
|
||||||
|
|
||||||
|
if (this._status >= ClaimStatus.MATCHED && this._matches.length > 0 && !this.isAmbiguous()) {
|
||||||
|
displayProfileName = this._matches[0].profile.display
|
||||||
|
displayProfileUrl = this._matches[0].profile.uri
|
||||||
|
displayProofUrl = this._matches[0].proof.request.uri
|
||||||
|
displayServiceProviderName = this._matches[0].about.name
|
||||||
|
displayServiceProviderId = this._matches[0].about.id
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
claimVersion: 1,
|
claimVersion: 2,
|
||||||
uri: this._uri,
|
uri: this._uri,
|
||||||
fingerprint: this._fingerprint,
|
proofs: [this._fingerprint],
|
||||||
|
matches: this._matches.map(x => x.toJSON()),
|
||||||
status: this._status,
|
status: this._status,
|
||||||
matches: this._matches,
|
display: {
|
||||||
verification: this._verification
|
profileName: displayProfileName,
|
||||||
|
profileUrl: displayProfileUrl,
|
||||||
|
proofUrl: displayProofUrl,
|
||||||
|
serviceProviderName: displayServiceProviderName,
|
||||||
|
serviceProviderId: displayServiceProviderId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Claim
|
/**
|
||||||
|
* @ignore
|
||||||
|
* @param {object} claimObject - JSON representation of a claim
|
||||||
|
* @returns {Claim | Error} Parsed claim
|
||||||
|
*/
|
||||||
|
function importJsonClaimVersion1 (claimObject) {
|
||||||
|
if (!('claimVersion' in claimObject && claimObject.claimVersion === 1)) {
|
||||||
|
return new Error('Invalid claim')
|
||||||
|
}
|
||||||
|
|
||||||
|
const claim = new Claim()
|
||||||
|
|
||||||
|
claim._uri = claimObject.uri
|
||||||
|
claim._fingerprint = claimObject.fingerprint
|
||||||
|
claim._matches = claimObject.matches.map(x => new ServiceProvider(x))
|
||||||
|
|
||||||
|
if (claimObject.status === 'init') {
|
||||||
|
claim._status = 100
|
||||||
|
}
|
||||||
|
if (claimObject.status === 'matched') {
|
||||||
|
if (claimObject.matches.length === 0) {
|
||||||
|
claim._status = 301
|
||||||
|
}
|
||||||
|
claim._status = 101
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!('result' in claimObject.verification && 'errors' in claimObject.verification)) {
|
||||||
|
claim._status = 400
|
||||||
|
}
|
||||||
|
if (claimObject.verification.errors.length > 0) {
|
||||||
|
claim._status = 400
|
||||||
|
}
|
||||||
|
if (claimObject.verification.result && claimObject.verification.proof.viaProxy) {
|
||||||
|
claim._status = 201
|
||||||
|
}
|
||||||
|
if (claimObject.verification.result && !claimObject.verification.proof.viaProxy) {
|
||||||
|
claim._status = 200
|
||||||
|
}
|
||||||
|
|
||||||
|
return claim
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ignore
|
||||||
|
* @param {object} claimObject - JSON representation of a claim
|
||||||
|
* @returns {Claim | Error} Parsed claim
|
||||||
|
*/
|
||||||
|
function importJsonClaimVersion2 (claimObject) {
|
||||||
|
if (!('claimVersion' in claimObject && claimObject.claimVersion === 2)) {
|
||||||
|
return new Error('Invalid claim')
|
||||||
|
}
|
||||||
|
|
||||||
|
const claim = new Claim()
|
||||||
|
|
||||||
|
claim._uri = claimObject.uri
|
||||||
|
claim._fingerprint = claimObject.proofs[0]
|
||||||
|
claim._matches = claimObject.matches.map(x => new ServiceProvider(x))
|
||||||
|
claim._status = claimObject.status
|
||||||
|
|
||||||
|
return claim
|
||||||
|
}
|
||||||
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/dev\.to\/(.*)\/(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'devto'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: match[1],
|
|
||||||
uri: `https://dev.to/${match[1]}`,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: uri,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.NOCORS,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `https://dev.to/api/articles/${match[1]}/${match[2]}`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['body_markdown']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://dev.to/alice/post',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://dev.to/alice/post/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice/post',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/(.*)\/u\/(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'discourse'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: true
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: `${match[2]}@${match[1]}`,
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: uri,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.NOCORS,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `https://${match[1]}/u/${match[2]}.json`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['user', 'bio_raw']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/u/alice',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/u/alice/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^dns:([a-zA-Z0-9.\-_]*)(?:\?(.*))?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'dns'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: match[1],
|
|
||||||
uri: `https://${match[1]}`,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: null,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.DNS,
|
|
||||||
access: E.ProofAccess.SERVER,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
domain: match[1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['records', 'txt']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'dns:domain.org',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'dns:domain.org?type=TXT',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/(.*)\/(.*)\/gitea_proof\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'gitea'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: true
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: `${match[2]}@${match[1]}`,
|
|
||||||
uri: `https://${match[1]}/${match[2]}`,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: uri,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.NOCORS,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `https://${match[1]}/api/v1/repos/${match[2]}/gitea_proof`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.EQUALS,
|
|
||||||
path: ['description']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice/gitea_proof',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice/gitea_proof/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice/other_proof',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/gist\.github\.com\/(.*)\/(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'github'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: match[1],
|
|
||||||
uri: `https://github.com/${match[1]}`,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: uri,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.GENERIC,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `https://api.github.com/gists/${match[2]}`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['files', 'openpgp.md', 'content']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://gist.github.com/Alice/123456789',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://gist.github.com/Alice/123456789/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/Alice/123456789',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/news\.ycombinator\.com\/user\?id=(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'hackernews'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: match[1],
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: `https://hacker-news.firebaseio.com/v0/user/${match[1]}.json`,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.NOCORS,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `https://hacker-news.firebaseio.com/v0/user/${match[1]}.json`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['about']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://news.ycombinator.com/user?id=Alice',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://news.ycombinator.com/user?id=Alice/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/user?id=Alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const list = [
|
|
||||||
'dns',
|
|
||||||
'irc',
|
|
||||||
'xmpp',
|
|
||||||
'matrix',
|
|
||||||
'telegram',
|
|
||||||
'twitter',
|
|
||||||
'reddit',
|
|
||||||
'liberapay',
|
|
||||||
'lichess',
|
|
||||||
'hackernews',
|
|
||||||
'lobsters',
|
|
||||||
'devto',
|
|
||||||
'gitea',
|
|
||||||
'gitlab',
|
|
||||||
'github',
|
|
||||||
'mastodon',
|
|
||||||
'pleroma',
|
|
||||||
'discourse',
|
|
||||||
'owncast',
|
|
||||||
'stackexchange'
|
|
||||||
]
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
dns: require('./dns'),
|
|
||||||
irc: require('./irc'),
|
|
||||||
xmpp: require('./xmpp'),
|
|
||||||
matrix: require('./matrix'),
|
|
||||||
telegram: require('./telegram'),
|
|
||||||
twitter: require('./twitter'),
|
|
||||||
reddit: require('./reddit'),
|
|
||||||
liberapay: require('./liberapay'),
|
|
||||||
lichess: require('./lichess'),
|
|
||||||
hackernews: require('./hackernews'),
|
|
||||||
lobsters: require('./lobsters'),
|
|
||||||
devto: require('./devto'),
|
|
||||||
gitea: require('./gitea'),
|
|
||||||
gitlab: require('./gitlab'),
|
|
||||||
github: require('./github'),
|
|
||||||
mastodon: require('./mastodon'),
|
|
||||||
pleroma: require('./pleroma'),
|
|
||||||
discourse: require('./discourse'),
|
|
||||||
owncast: require('./owncast'),
|
|
||||||
stackexchange: require('./stackexchange')
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.list = list
|
|
||||||
exports.data = data
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^irc:\/\/(.*)\/([a-zA-Z0-9\-[\]\\`_^{|}]*)/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'communication',
|
|
||||||
name: 'irc'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: `irc://${match[1]}/${match[2]}`,
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: null,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.IRC,
|
|
||||||
access: E.ProofAccess.SERVER,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
domain: match[1],
|
|
||||||
nick: match[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'irc://chat.ircserver.org/Alice1',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'irc://chat.ircserver.org/alice?param=123',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'irc://chat.ircserver.org/alice_bob',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://chat.ircserver.org/alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/liberapay\.com\/(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'liberapay'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: match[1],
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: uri,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.GENERIC,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `https://liberapay.com/${match[1]}/public.json`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['statements', 'content']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://liberapay.com/alice',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://liberapay.com/alice/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/lichess\.org\/@\/(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'lichess'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: match[1],
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: `https://lichess.org/api/user/${match[1]}`,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.GENERIC,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `https://lichess.org/api/user/${match[1]}`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.FINGERPRINT,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['profile', 'links']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://lichess.org/@/Alice',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://lichess.org/@/Alice/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/@/Alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/lobste\.rs\/u\/(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'lobsters'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: match[1],
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: `https://lobste.rs/u/${match[1]}.json`,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.NOCORS,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `https://lobste.rs/u/${match[1]}.json`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['about']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://lobste.rs/u/Alice',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://lobste.rs/u/Alice/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/u/Alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/(.*)\/@(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'mastodon'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: true
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: `@${match[2]}@${match[1]}`,
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: uri,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.GENERIC,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: uri,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.FINGERPRINT,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['attachment', 'value']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/@alice',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/@alice/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,93 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
const queryString = require('query-string')
|
|
||||||
|
|
||||||
const reURI = /^matrix:u\/(?:@)?([^@:]*:[^?]*)(\?.*)?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
if (!match[2]) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const params = queryString.parse(match[2])
|
|
||||||
|
|
||||||
if (!('org.keyoxide.e' in params && 'org.keyoxide.r' in params)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const profileUrl = `https://matrix.to/#/@${match[1]}`
|
|
||||||
const eventUrl = `https://matrix.to/#/${params['org.keyoxide.r']}/${params['org.keyoxide.e']}`
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'communication',
|
|
||||||
name: 'matrix'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: `@${match[1]}`,
|
|
||||||
uri: profileUrl,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: eventUrl,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.MATRIX,
|
|
||||||
access: E.ProofAccess.GRANTED,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
eventId: params['org.keyoxide.e'],
|
|
||||||
roomId: params['org.keyoxide.r']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['content', 'body']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri:
|
|
||||||
'matrix:u/alice:matrix.domain.org?org.keyoxide.r=!123:domain.org&org.keyoxide.e=$123',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'matrix:u/alice:matrix.domain.org',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'xmpp:alice@domain.org',
|
|
||||||
shouldMatch: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/@alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/(.*)/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'owncast'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: true
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: match[1],
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: `${uri}/api/config`,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.GENERIC,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: `${uri}/api/config`,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.FINGERPRINT,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['socialHandles', 'url']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://live.domain.org',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://live.domain.org/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/live',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/live/',
|
|
||||||
shouldMatch: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/(.*)\/users\/(.*)\/?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'pleroma'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: true
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: `@${match[2]}@${match[1]}`,
|
|
||||||
uri: uri,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: uri,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.HTTP,
|
|
||||||
access: E.ProofAccess.GENERIC,
|
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
|
||||||
url: uri,
|
|
||||||
format: E.ProofFormat.JSON
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.FINGERPRINT,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: ['summary']
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/users/alice',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/users/alice/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,73 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^https:\/\/twitter\.com\/(.*)\/status\/([0-9]*)(?:\?.*)?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'web',
|
|
||||||
name: 'twitter'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: `@${match[1]}`,
|
|
||||||
uri: `https://twitter.com/${match[1]}`,
|
|
||||||
qr: null
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: uri,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.TWITTER,
|
|
||||||
access: E.ProofAccess.GRANTED,
|
|
||||||
format: E.ProofFormat.TEXT,
|
|
||||||
data: {
|
|
||||||
tweetId: match[2]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'https://twitter.com/alice/status/1234567890123456789',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://twitter.com/alice/status/1234567890123456789/',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org/alice/status/1234567890123456789',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
|
@ -1,74 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
const reURI = /^xmpp:([a-zA-Z0-9.\-_]*)@([a-zA-Z0-9.\-_]*)(?:\?(.*))?/
|
|
||||||
|
|
||||||
const processURI = (uri) => {
|
|
||||||
const match = uri.match(reURI)
|
|
||||||
|
|
||||||
return {
|
|
||||||
serviceprovider: {
|
|
||||||
type: 'communication',
|
|
||||||
name: 'xmpp'
|
|
||||||
},
|
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: false
|
|
||||||
},
|
|
||||||
profile: {
|
|
||||||
display: `${match[1]}@${match[2]}`,
|
|
||||||
uri: uri,
|
|
||||||
qr: uri
|
|
||||||
},
|
|
||||||
proof: {
|
|
||||||
uri: null,
|
|
||||||
request: {
|
|
||||||
fetcher: E.Fetcher.XMPP,
|
|
||||||
access: E.ProofAccess.SERVER,
|
|
||||||
format: E.ProofFormat.TEXT,
|
|
||||||
data: {
|
|
||||||
id: `${match[1]}@${match[2]}`,
|
|
||||||
field: 'note'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
claim: {
|
|
||||||
format: E.ClaimFormat.URI,
|
|
||||||
relation: E.ClaimRelation.CONTAINS,
|
|
||||||
path: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const tests = [
|
|
||||||
{
|
|
||||||
uri: 'xmpp:alice@domain.org',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'xmpp:alice@domain.org?omemo-sid-123456789=A1B2C3D4E5F6G7H8I9',
|
|
||||||
shouldMatch: true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
uri: 'https://domain.org',
|
|
||||||
shouldMatch: false
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
25
src/constants.js
Normal file
25
src/constants.js
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Contains constant values
|
||||||
|
* @module constants
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* doip.js library version
|
||||||
|
* @constant {string}
|
||||||
|
*/
|
||||||
|
export const version = '1.2.9+myriaiton.1'
|
|
@ -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
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const E = require('./enums')
|
import { ProxyPolicy } from './enums.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Contains default values
|
* Contains default values
|
||||||
|
@ -21,46 +21,34 @@ const E = require('./enums')
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default options used throughout the library
|
* The default claim verification config used throughout the library
|
||||||
* @constant {object}
|
* @type {import('./types').VerificationConfig}
|
||||||
* @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.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.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
|
|
||||||
* @property {object} claims.twitter - Options related to the verification of Twitter claims
|
|
||||||
* @property {string|null} claims.twitter.bearerToken - The Twitter API's bearer token ({@link https://developer.twitter.com/en/docs/authentication/oauth-2-0/bearer-tokens|Twitter docs})
|
|
||||||
*/
|
*/
|
||||||
const opts = {
|
export const opts = {
|
||||||
proxy: {
|
proxy: {
|
||||||
hostname: null,
|
hostname: null,
|
||||||
policy: E.ProxyPolicy.NEVER
|
policy: ProxyPolicy.NEVER
|
||||||
},
|
},
|
||||||
claims: {
|
claims: {
|
||||||
|
activitypub: {
|
||||||
|
url: null,
|
||||||
|
privateKey: null
|
||||||
|
},
|
||||||
irc: {
|
irc: {
|
||||||
nick: null
|
nick: null,
|
||||||
|
sasl: []
|
||||||
},
|
},
|
||||||
matrix: {
|
matrix: {
|
||||||
instance: null,
|
instance: null,
|
||||||
accessToken: null
|
accessToken: null
|
||||||
},
|
},
|
||||||
|
telegram: {
|
||||||
|
token: null
|
||||||
|
},
|
||||||
xmpp: {
|
xmpp: {
|
||||||
service: null,
|
service: null,
|
||||||
username: null,
|
username: null,
|
||||||
password: null
|
password: null
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
bearerToken: null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.opts = opts
|
|
||||||
|
|
167
src/enums.js
167
src/enums.js
|
@ -23,7 +23,7 @@ limitations under the License.
|
||||||
* @readonly
|
* @readonly
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
const ProxyPolicy = {
|
export const ProxyPolicy = {
|
||||||
/** Proxy usage decision depends on environment and service provider */
|
/** Proxy usage decision depends on environment and service provider */
|
||||||
ADAPTIVE: 'adaptive',
|
ADAPTIVE: 'adaptive',
|
||||||
/** Always use a proxy */
|
/** Always use a proxy */
|
||||||
|
@ -31,108 +31,183 @@ const ProxyPolicy = {
|
||||||
/** Never use a proxy, skip a verification if a proxy is inevitable */
|
/** Never use a proxy, skip a verification if a proxy is inevitable */
|
||||||
NEVER: 'never'
|
NEVER: 'never'
|
||||||
}
|
}
|
||||||
Object.freeze(ProxyPolicy)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Methods for fetching proofs
|
* Methods for fetching proofs
|
||||||
* @readonly
|
* @readonly
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
const Fetcher = {
|
export const Fetcher = {
|
||||||
/** Basic HTTP requests */
|
/** HTTP requests to ActivityPub */
|
||||||
HTTP: 'http',
|
ACTIVITYPUB: 'activitypub',
|
||||||
|
/** ASPE HTTP requests */
|
||||||
|
ASPE: 'aspe',
|
||||||
/** DNS module from Node.js */
|
/** DNS module from Node.js */
|
||||||
DNS: 'dns',
|
DNS: 'dns',
|
||||||
|
/** GraphQL over HTTP requests */
|
||||||
|
GRAPHQL: 'graphql',
|
||||||
|
/** Basic HTTP requests */
|
||||||
|
HTTP: 'http',
|
||||||
/** IRC module from Node.js */
|
/** IRC module from Node.js */
|
||||||
IRC: 'irc',
|
IRC: 'irc',
|
||||||
/** XMPP module from Node.js */
|
|
||||||
XMPP: 'xmpp',
|
|
||||||
/** HTTP request to Matrix API */
|
/** HTTP request to Matrix API */
|
||||||
MATRIX: 'matrix',
|
MATRIX: 'matrix',
|
||||||
|
/** HKP and WKS request for OpenPGP */
|
||||||
|
OPENPGP: 'openpgp',
|
||||||
/** HTTP request to Telegram API */
|
/** HTTP request to Telegram API */
|
||||||
TELEGRAM: 'telegram',
|
TELEGRAM: 'telegram',
|
||||||
/** HTTP request to Twitter API */
|
/** XMPP module from Node.js */
|
||||||
TWITTER: 'twitter'
|
XMPP: 'xmpp'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entity encoding format
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const EntityEncodingFormat = {
|
||||||
|
/** No special formatting */
|
||||||
|
PLAIN: 'plain',
|
||||||
|
/** HTML encoded entities */
|
||||||
|
HTML: 'html',
|
||||||
|
/** XML encoded entities */
|
||||||
|
XML: 'xml'
|
||||||
}
|
}
|
||||||
Object.freeze(Fetcher)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Levels of access restriction for proof fetching
|
* Levels of access restriction for proof fetching
|
||||||
* @readonly
|
* @readonly
|
||||||
* @enum {number}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
const ProofAccess = {
|
export const ProofAccessRestriction = {
|
||||||
/** Any HTTP request will work */
|
/** Any HTTP request will work */
|
||||||
GENERIC: 0,
|
NONE: 'none',
|
||||||
/** CORS requests are denied */
|
/** CORS requests are denied */
|
||||||
NOCORS: 1,
|
NOCORS: 'nocors',
|
||||||
/** HTTP requests must contain API or access tokens */
|
/** HTTP requests must contain API or access tokens */
|
||||||
GRANTED: 2,
|
GRANTED: 'granted',
|
||||||
/** Not accessible by HTTP request, needs server software */
|
/** Not accessible by HTTP request, needs server software */
|
||||||
SERVER: 3
|
SERVER: 'server'
|
||||||
}
|
}
|
||||||
Object.freeze(ProofAccess)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format of proof
|
* Format of proof
|
||||||
* @readonly
|
* @readonly
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
const ProofFormat = {
|
export const ProofFormat = {
|
||||||
/** JSON format */
|
/** JSON format */
|
||||||
JSON: 'json',
|
JSON: 'json',
|
||||||
/** Plaintext format */
|
/** Plaintext format */
|
||||||
TEXT: 'text'
|
TEXT: 'text'
|
||||||
}
|
}
|
||||||
Object.freeze(ProofFormat)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Format of claim
|
* Format of claim
|
||||||
* @readonly
|
* @readonly
|
||||||
* @enum {number}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
const ClaimFormat = {
|
export const ClaimFormat = {
|
||||||
/** `openpgp4fpr:123123123` */
|
/** `openpgp4fpr:123123123` */
|
||||||
URI: 0,
|
URI: 'uri',
|
||||||
/** `123123123` */
|
/** `123123123` */
|
||||||
FINGERPRINT: 1
|
FINGERPRINT: 'fingerprint'
|
||||||
}
|
}
|
||||||
Object.freeze(ClaimFormat)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How to find the claim inside the proof's JSON data
|
* How to find the proof inside the fetched data
|
||||||
* @readonly
|
* @readonly
|
||||||
* @enum {number}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
const ClaimRelation = {
|
export const ClaimRelation = {
|
||||||
/** Claim is somewhere in the JSON field's textual content */
|
/** Claim is somewhere in the JSON field's textual content */
|
||||||
CONTAINS: 0,
|
CONTAINS: 'contains',
|
||||||
/** Claim is equal to the JSON field's textual content */
|
/** Claim is equal to the JSON field's textual content */
|
||||||
EQUALS: 1,
|
EQUALS: 'equals',
|
||||||
/** Claim is equal to an element of the JSON field's array of strings */
|
/** Claim is equal to an element of the JSON field's array of strings */
|
||||||
ONEOF: 2
|
ONEOF: 'oneof'
|
||||||
}
|
}
|
||||||
Object.freeze(ClaimRelation)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Status of the Claim instance
|
* Status of the Claim instance
|
||||||
* @readonly
|
* @readonly
|
||||||
|
* @enum {number}
|
||||||
|
*/
|
||||||
|
export const ClaimStatus = {
|
||||||
|
/** Claim has been initialized */
|
||||||
|
INIT: 100,
|
||||||
|
/** Claim has matched its URI to candidate claim definitions */
|
||||||
|
MATCHED: 101,
|
||||||
|
/** Claim was successfully verified */
|
||||||
|
VERIFIED: 200,
|
||||||
|
/** Claim was successfully verified using proxied data */
|
||||||
|
VERIFIED_VIA_PROXY: 201,
|
||||||
|
/** Unknown matching error */
|
||||||
|
MATCHING_ERROR: 300,
|
||||||
|
/** No matched service providers */
|
||||||
|
NO_MATCHES: 301,
|
||||||
|
/** Unknown matching error */
|
||||||
|
VERIFICATION_ERROR: 400,
|
||||||
|
/** No proof found in data returned by service providers */
|
||||||
|
NO_PROOF_FOUND: 401
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile type
|
||||||
|
* @readonly
|
||||||
* @enum {string}
|
* @enum {string}
|
||||||
*/
|
*/
|
||||||
const ClaimStatus = {
|
export const ProfileType = {
|
||||||
/** Claim has been initialized */
|
/** ASP profile */
|
||||||
INIT: 'init',
|
ASP: 'asp',
|
||||||
/** Claim has matched its URI to candidate claim definitions */
|
/** OpenPGP profile */
|
||||||
MATCHED: 'matched',
|
OPENPGP: 'openpgp'
|
||||||
/** Claim has verified one or multiple candidate claim definitions */
|
|
||||||
VERIFIED: 'verified'
|
|
||||||
}
|
}
|
||||||
Object.freeze(ClaimStatus)
|
|
||||||
|
|
||||||
exports.ProxyPolicy = ProxyPolicy
|
/**
|
||||||
exports.Fetcher = Fetcher
|
* Public key type
|
||||||
exports.ProofAccess = ProofAccess
|
* @readonly
|
||||||
exports.ProofFormat = ProofFormat
|
* @enum {string}
|
||||||
exports.ClaimFormat = ClaimFormat
|
*/
|
||||||
exports.ClaimRelation = ClaimRelation
|
export const PublicKeyType = {
|
||||||
exports.ClaimStatus = ClaimStatus
|
EDDSA: 'eddsa',
|
||||||
|
ES256: 'es256',
|
||||||
|
OPENPGP: 'openpgp',
|
||||||
|
UNKNOWN: 'unknown',
|
||||||
|
NONE: 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public key format
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const PublicKeyEncoding = {
|
||||||
|
PEM: 'pem',
|
||||||
|
JWK: 'jwk',
|
||||||
|
ARMORED_PGP: 'armored_pgp',
|
||||||
|
NONE: 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method to fetch the public key
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const PublicKeyFetchMethod = {
|
||||||
|
ASPE: 'aspe',
|
||||||
|
HKP: 'hkp',
|
||||||
|
WKD: 'wkd',
|
||||||
|
HTTP: 'http',
|
||||||
|
NONE: 'none'
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protocol to query OpenPGP public keys
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
export const OpenPgpQueryProtocol = {
|
||||||
|
HKP: 'hkp',
|
||||||
|
WKD: 'wkd'
|
||||||
|
}
|
||||||
|
|
104
src/fetcher/activitypub.js
Normal file
104
src/fetcher/activitypub.js
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Fetch proofs using ActivityPub HTTP requests
|
||||||
|
* @module fetcher/activitypub
|
||||||
|
* @example
|
||||||
|
* import { fetcher } from 'doipjs';
|
||||||
|
* const data = await fetcher.activitypub.fn({ url: 'https://domain.example/@alice' });
|
||||||
|
*/
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import isURL from 'validator/lib/isURL.js'
|
||||||
|
import { isNode } from 'browser-or-node'
|
||||||
|
import crypto from 'crypto'
|
||||||
|
import { version } from '../constants.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default timeout after which the fetch is aborted
|
||||||
|
* @constant
|
||||||
|
* @type {number}
|
||||||
|
* @default 5000
|
||||||
|
*/
|
||||||
|
export const timeout = 5000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a fetch request
|
||||||
|
* @function
|
||||||
|
* @param {object} data - Data used in the request
|
||||||
|
* @param {string} data.url - The URL of the account to verify
|
||||||
|
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||||
|
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||||
|
* @returns {Promise<object>} The fetched ActivityPub object
|
||||||
|
*/
|
||||||
|
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) => {
|
||||||
|
(async () => {
|
||||||
|
let isConfigured = false
|
||||||
|
try {
|
||||||
|
isURL(opts.claims.activitypub.url)
|
||||||
|
isConfigured = true
|
||||||
|
} catch (_) {}
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const { host, pathname, search } = new URL(data.url)
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
host,
|
||||||
|
date: now.toUTCString(),
|
||||||
|
accept: 'application/activity+json',
|
||||||
|
// @ts-ignore
|
||||||
|
'User-Agent': `doipjs/${version}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isConfigured && isNode) {
|
||||||
|
// Generate the signature
|
||||||
|
const signedString = `(request-target): get ${pathname}${search}\nhost: ${host}\ndate: ${now.toUTCString()}`
|
||||||
|
const sign = crypto.createSign('SHA256')
|
||||||
|
sign.write(signedString)
|
||||||
|
sign.end()
|
||||||
|
const signatureSig = sign.sign(opts.claims.activitypub.privateKey.replace(/\\n/g, '\n'), 'base64')
|
||||||
|
headers.signature = `keyId="${opts.claims.activitypub.url}#main-key",headers="(request-target) host date",signature="${signatureSig}",algorithm="rsa-sha256"`
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.get(data.url,
|
||||||
|
{
|
||||||
|
headers
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return res.data
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
resolve(res)
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
reject(error)
|
||||||
|
})
|
||||||
|
})()
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||||
|
clearTimeout(timeoutHandle)
|
||||||
|
})
|
||||||
|
}
|
90
src/fetcher/aspe.js
Normal file
90
src/fetcher/aspe.js
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -13,59 +13,64 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const jsEnv = require('browser-or-node')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Fetch proofs using DNS TXT records
|
||||||
* @module fetcher/dns
|
* @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'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request's timeout value in milliseconds
|
* Default timeout after which the fetch is aborted
|
||||||
* @constant {number} timeout
|
* @constant
|
||||||
|
* @type {number}
|
||||||
|
* @default 5000
|
||||||
*/
|
*/
|
||||||
module.exports.timeout = 5000
|
export const timeout = 5000
|
||||||
|
|
||||||
if (jsEnv.isNode) {
|
/**
|
||||||
const dns = require('dns')
|
* Execute a fetch request
|
||||||
|
* @function
|
||||||
|
* @param {object} data - Data used in the request
|
||||||
|
* @param {string} data.domain - The targeted domain
|
||||||
|
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||||
|
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||||
|
* @returns {Promise<object>} The fetched DNS records
|
||||||
|
*/
|
||||||
|
export async function fn (data, opts) {
|
||||||
|
if (isBrowser) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
let timeoutHandle
|
||||||
* Execute a fetch request
|
const timeoutPromise = new Promise((resolve, reject) => {
|
||||||
* @function
|
timeoutHandle = setTimeout(
|
||||||
* @async
|
() => reject(new Error('Request was timed out')),
|
||||||
* @param {object} data - Data used in the request
|
data.fetcherTimeout ? data.fetcherTimeout : timeout
|
||||||
* @param {string} data.domain - The targeted domain
|
)
|
||||||
* @returns {object}
|
})
|
||||||
*/
|
|
||||||
module.exports.fn = async (data, opts) => {
|
|
||||||
let timeoutHandle
|
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
|
||||||
timeoutHandle = setTimeout(
|
|
||||||
() => reject(new Error('Request was timed out')),
|
|
||||||
data.fetcherTimeout ? data.fetcherTimeout : module.exports.timeout
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchPromise = new Promise((resolve, reject) => {
|
const fetchPromise = new Promise((resolve, reject) => {
|
||||||
dns.resolveTxt(data.domain, (err, records) => {
|
dns.resolveTxt(data.domain, (err, records) => {
|
||||||
if (err) {
|
if (err) {
|
||||||
reject(err)
|
reject(err)
|
||||||
return
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve({
|
||||||
|
domain: data.domain,
|
||||||
|
records: {
|
||||||
|
txt: records
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve({
|
|
||||||
domain: data.domain,
|
|
||||||
records: {
|
|
||||||
txt: records
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||||
clearTimeout(timeoutHandle)
|
clearTimeout(timeoutHandle)
|
||||||
return result
|
})
|
||||||
})
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
module.exports.fn = null
|
|
||||||
}
|
}
|
||||||
|
|
88
src/fetcher/graphql.js
Normal file
88
src/fetcher/graphql.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Fetch proofs using GraphQL queries
|
||||||
|
* @module fetcher/graphql
|
||||||
|
* @example
|
||||||
|
* import { fetcher } from 'doipjs';
|
||||||
|
* const data = await fetcher.graphql.fn({ url: 'https://domain.example/graphql/v2', query: '{ "query": "..." }' });
|
||||||
|
*/
|
||||||
|
|
||||||
|
import axios from 'axios'
|
||||||
|
import { version } from '../constants.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default timeout after which the fetch is aborted
|
||||||
|
* @constant
|
||||||
|
* @type {number}
|
||||||
|
* @default 5000
|
||||||
|
*/
|
||||||
|
export const timeout = 5000
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a GraphQL query via HTTP request
|
||||||
|
* @function
|
||||||
|
* @param {object} data - Data used in the request
|
||||||
|
* @param {string} data.url - The URL pointing at the GraphQL HTTP endpoint
|
||||||
|
* @param {string} data.query - The GraphQL query to fetch the data containing the proof
|
||||||
|
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||||
|
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||||
|
* @returns {Promise<object>} The fetched GraphQL object
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
let jsonData
|
||||||
|
try {
|
||||||
|
jsonData = JSON.parse(data.query)
|
||||||
|
} catch (error) {
|
||||||
|
reject(new Error('Invalid GraphQL query object'))
|
||||||
|
}
|
||||||
|
|
||||||
|
axios.post(data.url, jsonData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
// @ts-ignore
|
||||||
|
'User-Agent': `doipjs/${version}`
|
||||||
|
},
|
||||||
|
validateStatus: function (status) {
|
||||||
|
return status >= 200 && status < 400
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
resolve(res.data)
|
||||||
|
})
|
||||||
|
.catch(e => {
|
||||||
|
reject(e)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||||
|
clearTimeout(timeoutHandle)
|
||||||
|
})
|
||||||
|
}
|
|
@ -13,34 +13,42 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const axios = require('axios')
|
|
||||||
const E = require('../enums')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Fetch proofs using HTTP requests
|
||||||
* @module fetcher/http
|
* @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'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request's timeout value in milliseconds
|
* Default timeout after which the fetch is aborted
|
||||||
* @constant {number} timeout
|
* @constant
|
||||||
|
* @type {number}
|
||||||
|
* @default 5000
|
||||||
*/
|
*/
|
||||||
module.exports.timeout = 5000
|
export const timeout = 5000
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a fetch request
|
* Execute a fetch request
|
||||||
* @function
|
* @function
|
||||||
* @async
|
* @param {object} data - Data used in the request
|
||||||
* @param {object} data - Data used in the request
|
* @param {string} data.url - The URL pointing at targeted content
|
||||||
* @param {string} data.url - The URL pointing at targeted content
|
* @param {string} data.format - The format of the targeted content
|
||||||
* @param {string} data.format - The format of the targeted content
|
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||||
* @returns {object|string}
|
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||||
|
* @returns {Promise<object|string>} The fetched JSON object or text
|
||||||
*/
|
*/
|
||||||
module.exports.fn = async (data, opts) => {
|
export async function fn (data, opts) {
|
||||||
let timeoutHandle
|
let timeoutHandle
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
const timeoutPromise = new Promise((resolve, reject) => {
|
||||||
timeoutHandle = setTimeout(
|
timeoutHandle = setTimeout(
|
||||||
() => reject(new Error('Request was timed out')),
|
() => reject(new Error('Request was timed out')),
|
||||||
data.fetcherTimeout ? data.fetcherTimeout : module.exports.timeout
|
data.fetcherTimeout ? data.fetcherTimeout : timeout
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -51,11 +59,12 @@ module.exports.fn = async (data, opts) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (data.format) {
|
switch (data.format) {
|
||||||
case E.ProofFormat.JSON:
|
case ProofFormat.JSON:
|
||||||
axios.get(data.url, {
|
axios.get(data.url, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'User-Agent': `doipjs/${require('../../package.json').version}`
|
// @ts-ignore
|
||||||
|
'User-Agent': `doipjs/${version}`
|
||||||
},
|
},
|
||||||
validateStatus: function (status) {
|
validateStatus: function (status) {
|
||||||
return status >= 200 && status < 400
|
return status >= 200 && status < 400
|
||||||
|
@ -68,7 +77,7 @@ module.exports.fn = async (data, opts) => {
|
||||||
reject(e)
|
reject(e)
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case E.ProofFormat.TEXT:
|
case ProofFormat.TEXT:
|
||||||
axios.get(data.url, {
|
axios.get(data.url, {
|
||||||
validateStatus: function (status) {
|
validateStatus: function (status) {
|
||||||
return status >= 200 && status < 400
|
return status >= 200 && status < 400
|
||||||
|
@ -88,8 +97,7 @@ module.exports.fn = async (data, opts) => {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||||
clearTimeout(timeoutHandle)
|
clearTimeout(timeoutHandle)
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,11 +13,13 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
export * as activitypub from './activitypub.js'
|
||||||
exports.dns = require('./dns')
|
export * as aspe from './aspe.js'
|
||||||
exports.http = require('./http')
|
export * as dns from './dns.js'
|
||||||
exports.irc = require('./irc')
|
export * as graphql from './graphql.js'
|
||||||
exports.matrix = require('./matrix')
|
export * as http from './http.js'
|
||||||
exports.telegram = require('./telegram')
|
export * as irc from './irc.js'
|
||||||
exports.twitter = require('./twitter')
|
export * as matrix from './matrix.js'
|
||||||
exports.xmpp = require('./xmpp')
|
export * as openpgp from './openpgp.js'
|
||||||
|
export * as telegram from './telegram.js'
|
||||||
|
export * as xmpp from './xmpp.js'
|
||||||
|
|
22
src/fetcher/index.minimal.js
Normal file
22
src/fetcher/index.minimal.js
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
export * as activitypub from './activitypub.js'
|
||||||
|
export * as aspe from './aspe.js'
|
||||||
|
export * as graphql from './graphql.js'
|
||||||
|
export * as http from './http.js'
|
||||||
|
export * as matrix from './matrix.js'
|
||||||
|
export * as openpgp from './openpgp.js'
|
||||||
|
export * as telegram from './telegram.js'
|
|
@ -13,84 +13,97 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const jsEnv = require('browser-or-node')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Fetch proofs using IRC
|
||||||
* @module fetcher/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'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request's timeout value in milliseconds
|
* Default timeout after which the fetch is aborted
|
||||||
* @constant {number} timeout
|
* @constant
|
||||||
|
* @type {number}
|
||||||
|
* @default 20000
|
||||||
*/
|
*/
|
||||||
module.exports.timeout = 20000
|
export const timeout = 20000
|
||||||
|
|
||||||
if (jsEnv.isNode) {
|
/**
|
||||||
const irc = require('irc-upd')
|
* Execute a fetch request
|
||||||
const validator = require('validator')
|
* @function
|
||||||
|
* @param {object} data - Data used in the request
|
||||||
|
* @param {string} data.nick - The nick of the targeted account
|
||||||
|
* @param {string} data.domain - The domain on which the targeted account is registered
|
||||||
|
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||||
|
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||||
|
* @returns {Promise<Array<string>>} The fetched proofs from an IRC account
|
||||||
|
*/
|
||||||
|
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) => {
|
||||||
* Execute a fetch request
|
try {
|
||||||
* @function
|
isAscii(opts.claims.irc.nick)
|
||||||
* @async
|
} catch (err) {
|
||||||
* @param {object} data - Data used in the request
|
throw new Error(`IRC fetcher was not set up properly (${err.message})`)
|
||||||
* @param {string} data.nick - The nick of the targeted account
|
}
|
||||||
* @param {string} data.domain - The domain on which the targeted account is registered
|
|
||||||
* @param {object} opts - Options used to enable the request
|
|
||||||
* @param {string} opts.claims.irc.nick - The nick to be used by the library to log in
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
module.exports.fn = async (data, opts) => {
|
|
||||||
let timeoutHandle
|
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
|
||||||
timeoutHandle = setTimeout(
|
|
||||||
() => reject(new Error('Request was timed out')),
|
|
||||||
data.fetcherTimeout ? data.fetcherTimeout : module.exports.timeout
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchPromise = new Promise((resolve, reject) => {
|
try {
|
||||||
try {
|
// Add sasl-related config if the server matches
|
||||||
validator.isAscii(opts.claims.irc.nick)
|
const matchedSaslConfig = opts.claims.irc.sasl.find(saslConfig => data.domain.match(new RegExp(saslConfig.domainRegex)) !== null)
|
||||||
} catch (err) {
|
const saslOptions = matchedSaslConfig
|
||||||
throw new Error(`IRC fetcher was not set up properly (${err.message})`)
|
? {
|
||||||
}
|
sasl: true,
|
||||||
|
userName: matchedSaslConfig.username,
|
||||||
try {
|
password: matchedSaslConfig.password
|
||||||
const client = new irc.Client(data.domain, opts.claims.irc.nick, {
|
|
||||||
port: 6697,
|
|
||||||
secure: true,
|
|
||||||
channels: [],
|
|
||||||
showErrors: false,
|
|
||||||
debug: false
|
|
||||||
})
|
|
||||||
const reKey = /[a-zA-Z0-9\-_]+\s+:\s(openpgp4fpr:.*)/
|
|
||||||
const reEnd = /End\sof\s.*\staxonomy./
|
|
||||||
const keys = []
|
|
||||||
|
|
||||||
client.addListener('registered', (message) => {
|
|
||||||
client.send(`PRIVMSG NickServ TAXONOMY ${data.nick}`)
|
|
||||||
})
|
|
||||||
client.addListener('notice', (nick, to, text, message) => {
|
|
||||||
if (reKey.test(text)) {
|
|
||||||
const match = text.match(reKey)
|
|
||||||
keys.push(match[1])
|
|
||||||
}
|
}
|
||||||
if (reEnd.test(text)) {
|
: {
|
||||||
client.disconnect()
|
sasl: false
|
||||||
resolve(keys)
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
const client = new irc.Client(data.domain, opts.claims.irc.nick, {
|
||||||
clearTimeout(timeoutHandle)
|
port: 6697,
|
||||||
return result
|
secure: true,
|
||||||
})
|
channels: [],
|
||||||
}
|
showErrors: false,
|
||||||
} else {
|
debug: false,
|
||||||
module.exports.fn = null
|
...saslOptions
|
||||||
|
})
|
||||||
|
const reKey = /[a-zA-Z0-9\-_]+\s+:\s((?:openpgp4fpr|aspe):.*)/
|
||||||
|
const reEnd = /End\sof\s.*\staxonomy./
|
||||||
|
const keys = []
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
client.addListener('registered', (message) => {
|
||||||
|
client.send(`PRIVMSG NickServ TAXONOMY ${data.nick}`)
|
||||||
|
})
|
||||||
|
// @ts-ignore
|
||||||
|
client.addListener('notice', (nick, to, text, message) => {
|
||||||
|
if (reKey.test(text)) {
|
||||||
|
const match = text.match(reKey)
|
||||||
|
keys.push(match[1])
|
||||||
|
}
|
||||||
|
if (reEnd.test(text)) {
|
||||||
|
client.disconnect()
|
||||||
|
resolve(keys)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||||
|
clearTimeout(timeoutHandle)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,44 +13,50 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const axios = require('axios')
|
|
||||||
const validator = require('validator')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Fetch proofs using Matrix messages
|
||||||
* @module fetcher/matrix
|
* @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'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request's timeout value in milliseconds
|
* Default timeout after which the fetch is aborted
|
||||||
* @constant {number} timeout
|
* @constant
|
||||||
|
* @type {number}
|
||||||
|
* @default 5000
|
||||||
*/
|
*/
|
||||||
module.exports.timeout = 5000
|
export const timeout = 5000
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a fetch request
|
* Execute a fetch request
|
||||||
* @function
|
* @function
|
||||||
* @async
|
* @param {object} data - Data used in the request
|
||||||
* @param {object} data - Data used in the request
|
* @param {string} data.eventId - The identifier of the targeted post
|
||||||
* @param {string} data.eventId - The identifier of the targeted post
|
* @param {string} data.roomId - The identifier of the room containing 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 {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||||
* @param {string} opts.claims.matrix.instance - The server hostname on which the library can log in
|
* @returns {Promise<object>} The fetched Matrix object
|
||||||
* @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 {object}
|
|
||||||
*/
|
*/
|
||||||
module.exports.fn = async (data, opts) => {
|
export async function fn (data, opts) {
|
||||||
let timeoutHandle
|
let timeoutHandle
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
const timeoutPromise = new Promise((resolve, reject) => {
|
||||||
timeoutHandle = setTimeout(
|
timeoutHandle = setTimeout(
|
||||||
() => reject(new Error('Request was timed out')),
|
() => reject(new Error('Request was timed out')),
|
||||||
data.fetcherTimeout ? data.fetcherTimeout : module.exports.timeout
|
data.fetcherTimeout ? data.fetcherTimeout : timeout
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const fetchPromise = new Promise((resolve, reject) => {
|
const fetchPromise = new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
validator.isFQDN(opts.claims.matrix.instance)
|
isFQDN(opts.claims.matrix.instance)
|
||||||
validator.isAscii(opts.claims.matrix.accessToken)
|
isAscii(opts.claims.matrix.accessToken)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Matrix fetcher was not set up properly (${err.message})`)
|
throw new Error(`Matrix fetcher was not set up properly (${err.message})`)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +64,11 @@ module.exports.fn = async (data, opts) => {
|
||||||
const url = `https://${opts.claims.matrix.instance}/_matrix/client/r0/rooms/${data.roomId}/event/${data.eventId}?access_token=${opts.claims.matrix.accessToken}`
|
const url = `https://${opts.claims.matrix.instance}/_matrix/client/r0/rooms/${data.roomId}/event/${data.eventId}?access_token=${opts.claims.matrix.accessToken}`
|
||||||
axios.get(url,
|
axios.get(url,
|
||||||
{
|
{
|
||||||
headers: { Accept: 'application/json' }
|
headers: {
|
||||||
|
Accept: 'application/json',
|
||||||
|
// @ts-ignore
|
||||||
|
'User-Agent': `doipjs/${version}`
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.then(res => {
|
.then(res => {
|
||||||
return res.data
|
return res.data
|
||||||
|
@ -71,8 +81,7 @@ module.exports.fn = async (data, opts) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||||
clearTimeout(timeoutHandle)
|
clearTimeout(timeoutHandle)
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
131
src/fetcher/openpgp.js
Normal file
131
src/fetcher/openpgp.js
Normal 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)
|
||||||
|
})
|
||||||
|
}
|
|
@ -13,48 +13,53 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const axios = require('axios')
|
|
||||||
const validator = require('validator')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Fetch proofs using Telegram groups
|
||||||
* @module fetcher/telegram
|
* @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'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The single request's timeout value in milliseconds
|
* Default timeout after which the fetch is aborted
|
||||||
* This fetcher makes two requests in total
|
* @constant
|
||||||
* @constant {number} timeout
|
* @type {number}
|
||||||
|
* @default 5000
|
||||||
*/
|
*/
|
||||||
module.exports.timeout = 5000
|
export const timeout = 5000
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute a fetch request
|
* Execute a fetch request
|
||||||
* @function
|
* @function
|
||||||
* @async
|
* @param {object} data - Data used in the request
|
||||||
* @param {object} data - Data used in the request
|
* @param {string} data.chat - Telegram public group name (slug)
|
||||||
* @param {string} data.chat - Telegram public chat username
|
* @param {string} data.user - Telegram username
|
||||||
* @param {string} data.user - Telegram user username
|
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||||
* @param {object} opts - Options used to enable the request
|
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||||
* @param {string} opts.claims.telegram.token - The Telegram Bot API token
|
* @returns {Promise<object|string>} The fetched Telegram object
|
||||||
* @returns {object|string}
|
|
||||||
*/
|
*/
|
||||||
module.exports.fn = async (data, opts) => {
|
export async function fn (data, opts) {
|
||||||
let timeoutHandle
|
let timeoutHandle
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
const timeoutPromise = new Promise((resolve, reject) => {
|
||||||
timeoutHandle = setTimeout(
|
timeoutHandle = setTimeout(
|
||||||
() => reject(new Error('Request was timed out')),
|
() => reject(new Error('Request was timed out')),
|
||||||
data.fetcherTimeout ? data.fetcherTimeout : module.exports.timeout
|
data.fetcherTimeout ? data.fetcherTimeout : timeout
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
const apiPromise = (method) => new Promise((resolve, reject) => {
|
const apiPromise = (/** @type {string} */ method) => new Promise((resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
validator.isAscii(opts.claims.telegram.token)
|
isAscii(opts.claims.telegram.token)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
throw new Error(`Telegram fetcher was not set up properly (${err.message})`)
|
throw new Error(`Telegram fetcher was not set up properly (${err.message})`)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!data.chat || !data.user) {
|
if (!(data.chat && data.user)) {
|
||||||
reject(new Error('Both chat name and user name must be provided'))
|
reject(new Error('Both chat name and user name must be provided'))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -63,7 +68,8 @@ module.exports.fn = async (data, opts) => {
|
||||||
axios.get(url, {
|
axios.get(url, {
|
||||||
headers: {
|
headers: {
|
||||||
Accept: 'application/json',
|
Accept: 'application/json',
|
||||||
'User-Agent': `doipjs/${require('../../package.json').version}`
|
// @ts-ignore
|
||||||
|
'User-Agent': `doipjs/${version}`
|
||||||
},
|
},
|
||||||
validateStatus: (status) => status === 200
|
validateStatus: (status) => status === 200
|
||||||
})
|
})
|
||||||
|
@ -103,8 +109,7 @@ module.exports.fn = async (data, opts) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||||
clearTimeout(timeoutHandle)
|
clearTimeout(timeoutHandle)
|
||||||
return result
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const axios = require('axios')
|
|
||||||
const validator = require('validator')
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @module fetcher/twitter
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The request's timeout value in milliseconds
|
|
||||||
* @constant {number} timeout
|
|
||||||
*/
|
|
||||||
module.exports.timeout = 5000
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Execute a fetch request
|
|
||||||
* @function
|
|
||||||
* @async
|
|
||||||
* @param {object} data - Data used in the request
|
|
||||||
* @param {number|string} data.tweetId - Identifier of the tweet
|
|
||||||
* @param {object} opts - Options used to enable the request
|
|
||||||
* @param {string} opts.claims.twitter.bearerToken - The Twitter API's bearer token
|
|
||||||
* @returns {object}
|
|
||||||
*/
|
|
||||||
module.exports.fn = async (data, opts) => {
|
|
||||||
let timeoutHandle
|
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
|
||||||
timeoutHandle = setTimeout(
|
|
||||||
() => reject(new Error('Request was timed out')),
|
|
||||||
data.fetcherTimeout ? data.fetcherTimeout : module.exports.timeout
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
const fetchPromise = new Promise((resolve, reject) => {
|
|
||||||
try {
|
|
||||||
validator.isAscii(opts.claims.twitter.bearerToken)
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(
|
|
||||||
`Twitter fetcher was not set up properly (${err.message})`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
axios.get(
|
|
||||||
`https://api.twitter.com/1.1/statuses/show.json?id=${data.tweetId}&tweet_mode=extended`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${opts.claims.twitter.bearerToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(data => {
|
|
||||||
return data.data
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
resolve(data.full_text)
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
|
||||||
clearTimeout(timeoutHandle)
|
|
||||||
return result
|
|
||||||
})
|
|
||||||
}
|
|
|
@ -13,133 +13,177 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const jsEnv = require('browser-or-node')
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Fetch proofs from XMPP accounts
|
||||||
* @module fetcher/xmpp
|
* @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'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The request's timeout value in milliseconds
|
* Default timeout after which the fetch is aborted
|
||||||
* @constant {number} timeout
|
* @constant
|
||||||
|
* @type {number}
|
||||||
|
* @default 5000
|
||||||
*/
|
*/
|
||||||
module.exports.timeout = 5000
|
export const timeout = 5000
|
||||||
|
|
||||||
if (jsEnv.isNode) {
|
let xmpp = null
|
||||||
const jsdom = require('jsdom')
|
let iqCaller = null
|
||||||
const { client, xml } = require('@xmpp/client')
|
|
||||||
const debug = require('@xmpp/debug')
|
|
||||||
const validator = require('validator')
|
|
||||||
|
|
||||||
let xmpp = null
|
/**
|
||||||
let iqCaller = null
|
* Start the XMPP client
|
||||||
|
* @ignore
|
||||||
const xmppStart = async (service, username, password) => {
|
* @function
|
||||||
return new Promise((resolve, reject) => {
|
* @param {import('../types').XmppClaimVerificationConfig} params - XMPP claim verification config
|
||||||
const xmpp = client({
|
* @returns {Promise<object>} The fetched proofs from an XMPP account
|
||||||
service: service,
|
*/
|
||||||
username: username,
|
const xmppStart = async (params) => {
|
||||||
password: password
|
return new Promise((resolve, reject) => {
|
||||||
})
|
const xmpp = client({ ...params })
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
debug(xmpp, true)
|
debug(xmpp, true)
|
||||||
}
|
|
||||||
const { iqCaller } = xmpp
|
|
||||||
xmpp.start()
|
|
||||||
xmpp.on('online', (address) => {
|
|
||||||
resolve({ xmpp: xmpp, iqCaller: iqCaller })
|
|
||||||
})
|
|
||||||
xmpp.on('error', (error) => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 {string} data.field - The vCard field to return (should be "note")
|
|
||||||
* @param {object} opts - Options used to enable the request
|
|
||||||
* @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 {object}
|
|
||||||
*/
|
|
||||||
module.exports.fn = async (data, opts) => {
|
|
||||||
try {
|
|
||||||
validator.isFQDN(opts.claims.xmpp.service)
|
|
||||||
validator.isAscii(opts.claims.xmpp.username)
|
|
||||||
validator.isAscii(opts.claims.xmpp.password)
|
|
||||||
} catch (err) {
|
|
||||||
throw new Error(`XMPP fetcher was not set up properly (${err.message})`)
|
|
||||||
}
|
}
|
||||||
|
const { iqCaller } = xmpp
|
||||||
if (!xmpp || xmpp.status !== 'online') {
|
xmpp.start()
|
||||||
const xmppStartRes = await xmppStart(
|
xmpp.on('online', _ => {
|
||||||
opts.claims.xmpp.service,
|
resolve({ xmpp, iqCaller })
|
||||||
opts.claims.xmpp.username,
|
|
||||||
opts.claims.xmpp.password
|
|
||||||
)
|
|
||||||
xmpp = xmppStartRes.xmpp
|
|
||||||
iqCaller = xmppStartRes.iqCaller
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await iqCaller.request(
|
|
||||||
xml('iq', { type: 'get', to: data.id }, xml('vCard', 'vcard-temp')),
|
|
||||||
30 * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
const vcardRow = response.getChild('vCard', 'vcard-temp').toString()
|
|
||||||
const dom = new jsdom.JSDOM(vcardRow)
|
|
||||||
|
|
||||||
let timeoutHandle
|
|
||||||
const timeoutPromise = new Promise((resolve, reject) => {
|
|
||||||
timeoutHandle = setTimeout(
|
|
||||||
() => reject(new Error('Request was timed out')),
|
|
||||||
data.fetcherTimeout ? data.fetcherTimeout : module.exports.timeout
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
xmpp.on('error', error => {
|
||||||
const fetchPromise = new Promise((resolve, reject) => {
|
reject(error)
|
||||||
try {
|
|
||||||
let vcard
|
|
||||||
|
|
||||||
switch (data.field.toLowerCase()) {
|
|
||||||
case 'desc':
|
|
||||||
case 'note':
|
|
||||||
vcard = dom.window.document.querySelector('note text')
|
|
||||||
if (!vcard) {
|
|
||||||
vcard = dom.window.document.querySelector('note')
|
|
||||||
}
|
|
||||||
if (!vcard) {
|
|
||||||
vcard = dom.window.document.querySelector('DESC')
|
|
||||||
}
|
|
||||||
if (vcard) {
|
|
||||||
vcard = vcard.textContent
|
|
||||||
} else {
|
|
||||||
throw new Error('No DESC or NOTE field found in vCard')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
vcard = dom.window.document.querySelector(data).textContent
|
|
||||||
break
|
|
||||||
}
|
|
||||||
xmpp.stop()
|
|
||||||
resolve(vcard)
|
|
||||||
} catch (error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
return Promise.race([fetchPromise, timeoutPromise]).then((result) => {
|
}
|
||||||
clearTimeout(timeoutHandle)
|
|
||||||
return result
|
/**
|
||||||
})
|
* Execute a fetch request
|
||||||
}
|
* @function
|
||||||
} else {
|
* @param {object} data - Data used in the request
|
||||||
module.exports.fn = null
|
* @param {string} data.id - The identifier of the targeted account
|
||||||
|
* @param {number} [data.fetcherTimeout] - Optional timeout for the fetcher
|
||||||
|
* @param {import('../types').VerificationConfig} [opts] - Options used to enable the request
|
||||||
|
* @returns {Promise<Array<string>>} The fetched proofs from an XMPP account
|
||||||
|
*/
|
||||||
|
export async function fn (data, opts) {
|
||||||
|
try {
|
||||||
|
isFQDN(opts.claims.xmpp.service)
|
||||||
|
isAscii(opts.claims.xmpp.username)
|
||||||
|
isAscii(opts.claims.xmpp.password)
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`XMPP fetcher was not set up properly (${err.message})`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!xmpp || xmpp.status !== 'online') {
|
||||||
|
const xmppStartRes = await xmppStart(opts.claims.xmpp)
|
||||||
|
xmpp = xmppStartRes.xmpp
|
||||||
|
iqCaller = xmppStartRes.iqCaller
|
||||||
|
}
|
||||||
|
|
||||||
|
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) => {
|
||||||
|
(async () => {
|
||||||
|
let completed = false
|
||||||
|
const proofs = []
|
||||||
|
|
||||||
|
// Try the ariadne-id pubsub request
|
||||||
|
if (!completed) {
|
||||||
|
try {
|
||||||
|
const response = await iqCaller.request(
|
||||||
|
xml('iq', { type: 'get', to: data.id }, xml('pubsub', 'http://jabber.org/protocol/pubsub', xml('items', { node: 'http://ariadne.id/protocol/proof' }))),
|
||||||
|
30 * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Traverse the XML response
|
||||||
|
response.getChild('pubsub').getChildren('items').forEach(items => {
|
||||||
|
if (items.attrs.node === 'http://ariadne.id/protocol/proof') {
|
||||||
|
items.getChildren('item').forEach(item => {
|
||||||
|
proofs.push(item.getChildText('value'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resolve(proofs)
|
||||||
|
completed = true
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the vcard4 pubsub request [backward compatibility]
|
||||||
|
if (!completed) {
|
||||||
|
try {
|
||||||
|
const response = await iqCaller.request(
|
||||||
|
xml('iq', { type: 'get', to: data.id }, xml('pubsub', 'http://jabber.org/protocol/pubsub', xml('items', { node: 'urn:xmpp:vcard4', max_items: '1' }))),
|
||||||
|
30 * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Traverse the XML response
|
||||||
|
response.getChild('pubsub').getChildren('items').forEach(items => {
|
||||||
|
if (items.attrs.node === 'urn:xmpp:vcard4') {
|
||||||
|
items.getChildren('item').forEach(item => {
|
||||||
|
if (item.attrs.id === 'current') {
|
||||||
|
const itemVcard = item.getChild('vcard', 'urn:ietf:params:xml:ns:vcard-4.0')
|
||||||
|
// Find the vCard URLs
|
||||||
|
itemVcard.getChildren('url').forEach(url => {
|
||||||
|
proofs.push(url.getChildText('uri'))
|
||||||
|
})
|
||||||
|
// Find the vCard notes
|
||||||
|
itemVcard.getChildren('note').forEach(note => {
|
||||||
|
proofs.push(note.getChildText('text'))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
resolve(proofs)
|
||||||
|
completed = true
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try the vcard-temp IQ request [backward compatibility]
|
||||||
|
if (!completed) {
|
||||||
|
try {
|
||||||
|
const response = await iqCaller.request(
|
||||||
|
xml('iq', { type: 'get', to: data.id }, xml('vCard', 'vcard-temp')),
|
||||||
|
30 * 1000
|
||||||
|
)
|
||||||
|
|
||||||
|
// Find the vCard URLs
|
||||||
|
response.getChild('vCard', 'vcard-temp').getChildren('URL').forEach(url => {
|
||||||
|
proofs.push(url.children[0])
|
||||||
|
})
|
||||||
|
// Find the vCard notes
|
||||||
|
response.getChild('vCard', 'vcard-temp').getChildren('NOTE').forEach(note => {
|
||||||
|
proofs.push(note.children[0])
|
||||||
|
})
|
||||||
|
response.getChild('vCard', 'vcard-temp').getChildren('DESC').forEach(note => {
|
||||||
|
proofs.push(note.children[0])
|
||||||
|
})
|
||||||
|
|
||||||
|
resolve(proofs)
|
||||||
|
completed = true
|
||||||
|
} catch (error) {
|
||||||
|
reject(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xmpp.stop()
|
||||||
|
})()
|
||||||
|
})
|
||||||
|
|
||||||
|
return Promise.race([fetchPromise, timeoutPromise]).finally(() => {
|
||||||
|
clearTimeout(timeoutHandle)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
37
src/index.js
37
src/index.js
|
@ -13,22 +13,23 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const Claim = require('./claim')
|
|
||||||
const claimDefinitions = require('./claimDefinitions')
|
|
||||||
const proofs = require('./proofs')
|
|
||||||
const keys = require('./keys')
|
|
||||||
const signatures = require('./signatures')
|
|
||||||
const enums = require('./enums')
|
|
||||||
const defaults = require('./defaults')
|
|
||||||
const utils = require('./utils')
|
|
||||||
const verifications = require('./verifications')
|
|
||||||
|
|
||||||
exports.Claim = Claim
|
/**
|
||||||
exports.claimDefinitions = claimDefinitions
|
* @module doipjs
|
||||||
exports.proofs = proofs
|
* @license Apache-2.0
|
||||||
exports.keys = keys
|
*/
|
||||||
exports.signatures = signatures
|
export { Profile } from './profile.js'
|
||||||
exports.enums = enums
|
export { Persona } from './persona.js'
|
||||||
exports.defaults = defaults
|
export { Claim } from './claim.js'
|
||||||
exports.utils = utils
|
export { ServiceProvider } from './serviceProvider.js'
|
||||||
exports.verifications = verifications
|
export * as ServiceProviderDefinitions from './serviceProviders/index.js'
|
||||||
|
export * as proofs from './proofs.js'
|
||||||
|
export * as openpgp from './openpgp.js'
|
||||||
|
export * as asp from './asp.js'
|
||||||
|
export * as signatures from './signatures.js'
|
||||||
|
export * as enums from './enums.js'
|
||||||
|
export * as defaults from './defaults.js'
|
||||||
|
export * as utils from './utils.js'
|
||||||
|
export * as verifications from './verifications.js'
|
||||||
|
export * as schemas from './schemas.js'
|
||||||
|
export * as fetcher from './fetcher/index.js'
|
||||||
|
|
|
@ -13,98 +13,112 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const axios = require('axios')
|
import axios from 'axios'
|
||||||
const validUrl = require('valid-url')
|
import { isUri } from 'valid-url'
|
||||||
const openpgp = require('openpgp')
|
import { readKey, PublicKey } from 'openpgp'
|
||||||
const HKP = require('@openpgp/hkp-client')
|
import HKP from '@openpgp/hkp-client'
|
||||||
const WKD = require('@openpgp/wkd-client')
|
import WKD from '@openpgp/wkd-client'
|
||||||
const Claim = require('./claim')
|
import { Claim } from './claim.js'
|
||||||
|
import { ProfileType, PublicKeyEncoding, PublicKeyFetchMethod, PublicKeyType } from './enums.js'
|
||||||
|
import { Profile } from './profile.js'
|
||||||
|
import { Persona } from './persona.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Functions related to the fetching and handling of keys
|
* Functions related to OpenPGP Profiles
|
||||||
* @module keys
|
* @module openpgp
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a public key using keyservers
|
* Fetch a public key using keyservers
|
||||||
* @function
|
* @function
|
||||||
* @param {string} identifier - Fingerprint or email address
|
* @param {string} identifier - Fingerprint or email address
|
||||||
* @param {string} [keyserverDomain=keys.openpgp.org] - Domain of the keyserver
|
* @param {string} [keyserverDomain] - Domain of the keyserver
|
||||||
* @returns {openpgp.PublicKey}
|
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||||
* @example
|
* @example
|
||||||
* const key1 = doip.keys.fetchHKP('alice@domain.tld');
|
* const key1 = doip.keys.fetchHKP('alice@domain.tld');
|
||||||
* const key2 = doip.keys.fetchHKP('123abc123abc');
|
* const key2 = doip.keys.fetchHKP('123abc123abc');
|
||||||
|
* const key3 = doip.keys.fetchHKP('123abc123abc', 'pgpkeys.eu');
|
||||||
*/
|
*/
|
||||||
const fetchHKP = async (identifier, keyserverDomain) => {
|
export async function fetchHKP (identifier, keyserverDomain = 'keys.openpgp.org') {
|
||||||
const keyserverBaseUrl = keyserverDomain
|
const keyserverBaseUrl = `https://${keyserverDomain ?? 'keys.openpgp.org'}`
|
||||||
? `https://${keyserverDomain}`
|
|
||||||
: 'https://keys.openpgp.org'
|
|
||||||
|
|
||||||
const hkp = new HKP(keyserverBaseUrl)
|
const hkp = new HKP(keyserverBaseUrl)
|
||||||
const lookupOpts = {
|
const lookupOpts = {
|
||||||
query: identifier
|
query: identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKey = await hkp
|
const publicKeyArmored = await hkp
|
||||||
.lookup(lookupOpts)
|
.lookup(lookupOpts)
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!publicKey) {
|
if (!publicKeyArmored) {
|
||||||
throw new Error('Key does not exist or could not be fetched')
|
throw new Error('Key does not exist or could not be fetched')
|
||||||
}
|
}
|
||||||
|
|
||||||
return await openpgp.readKey({
|
const publicKey = await readKey({
|
||||||
armoredKey: publicKey
|
armoredKey: publicKeyArmored
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw new Error(`Key could not be read (${error})`)
|
throw new Error(`Key could not be read (${error})`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const profile = await parsePublicKey(publicKey)
|
||||||
|
profile.publicKey.fetch.method = PublicKeyFetchMethod.HKP
|
||||||
|
profile.publicKey.fetch.query = identifier
|
||||||
|
|
||||||
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a public key using Web Key Directory
|
* Fetch a public key using Web Key Directory
|
||||||
* @function
|
* @function
|
||||||
* @param {string} identifier - Identifier of format 'username@domain.tld`
|
* @param {string} identifier - Identifier of format 'username@domain.tld`
|
||||||
* @returns {openpgp.PublicKey}
|
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||||
* @example
|
* @example
|
||||||
* const key = doip.keys.fetchWKD('alice@domain.tld');
|
* const key = doip.keys.fetchWKD('alice@domain.tld');
|
||||||
*/
|
*/
|
||||||
const fetchWKD = async (identifier) => {
|
export async function fetchWKD (identifier) {
|
||||||
const wkd = new WKD()
|
const wkd = new WKD()
|
||||||
const lookupOpts = {
|
const lookupOpts = {
|
||||||
email: identifier
|
email: identifier
|
||||||
}
|
}
|
||||||
|
|
||||||
const publicKey = await wkd
|
const publicKeyBinary = await wkd
|
||||||
.lookup(lookupOpts)
|
.lookup(lookupOpts)
|
||||||
.catch((error) => {
|
.catch((/** @type {Error} */ error) => {
|
||||||
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
||||||
})
|
})
|
||||||
|
|
||||||
if (!publicKey) {
|
if (!publicKeyBinary) {
|
||||||
throw new Error('Key does not exist or could not be fetched')
|
throw new Error('Key does not exist or could not be fetched')
|
||||||
}
|
}
|
||||||
|
|
||||||
return await openpgp.readKey({
|
const publicKey = await readKey({
|
||||||
binaryKey: publicKey
|
binaryKey: publicKeyBinary
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw new Error(`Key could not be read (${error})`)
|
throw new Error(`Key could not be read (${error})`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const profile = await parsePublicKey(publicKey)
|
||||||
|
profile.publicKey.fetch.method = PublicKeyFetchMethod.WKD
|
||||||
|
profile.publicKey.fetch.query = identifier
|
||||||
|
|
||||||
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a public key from Keybase
|
* Fetch a public key from Keybase
|
||||||
* @function
|
* @function
|
||||||
* @param {string} username - Keybase username
|
* @param {string} username - Keybase username
|
||||||
* @param {string} fingerprint - Fingerprint of key
|
* @param {string} fingerprint - Fingerprint of key
|
||||||
* @returns {openpgp.PublicKey}
|
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||||
* @example
|
* @example
|
||||||
* const key = doip.keys.fetchKeybase('alice', '123abc123abc');
|
* const key = doip.keys.fetchKeybase('alice', '123abc123abc');
|
||||||
*/
|
*/
|
||||||
const fetchKeybase = async (username, fingerprint) => {
|
export async function fetchKeybase (username, fingerprint) {
|
||||||
const keyLink = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}`
|
const keyLink = `https://keybase.io/${username}/pgp_keys.asc?fingerprint=${fingerprint}`
|
||||||
let rawKeyContent
|
let rawKeyContent
|
||||||
try {
|
try {
|
||||||
|
@ -114,29 +128,36 @@ const fetchKeybase = async (username, fingerprint) => {
|
||||||
responseType: 'text'
|
responseType: 'text'
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then((response) => {
|
.then((/** @type {import('axios').AxiosResponse} */ response) => {
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then((response) => response.data)
|
.then((/** @type {import('axios').AxiosResponse} */ response) => response.data)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
throw new Error(`Error fetching Keybase key: ${e.message}`)
|
throw new Error(`Error fetching Keybase key: ${e.message}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
return await openpgp.readKey({
|
const publicKey = await readKey({
|
||||||
armoredKey: rawKeyContent
|
armoredKey: rawKeyContent
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
throw new Error(`Key does not exist or could not be fetched (${error})`)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const profile = await parsePublicKey(publicKey)
|
||||||
|
profile.publicKey.fetch.method = PublicKeyFetchMethod.HTTP
|
||||||
|
profile.publicKey.fetch.query = null
|
||||||
|
profile.publicKey.fetch.resolvedUrl = keyLink
|
||||||
|
|
||||||
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a public key from plaintext data
|
* Get a public key from armored public key text data
|
||||||
* @function
|
* @function
|
||||||
* @param {string} rawKeyContent - Plaintext ASCII-formatted public key data
|
* @param {string} rawKeyContent - Plaintext ASCII-formatted public key data
|
||||||
* @returns {openpgp.PublicKey}
|
* @returns {Promise<Profile>} The profile from the armored public key
|
||||||
* @example
|
* @example
|
||||||
* const plainkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
* const plainkey = `-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||||
*
|
*
|
||||||
|
@ -146,29 +167,31 @@ const fetchKeybase = async (username, fingerprint) => {
|
||||||
* -----END PGP PUBLIC KEY BLOCK-----`
|
* -----END PGP PUBLIC KEY BLOCK-----`
|
||||||
* const key = doip.keys.fetchPlaintext(plainkey);
|
* const key = doip.keys.fetchPlaintext(plainkey);
|
||||||
*/
|
*/
|
||||||
const fetchPlaintext = async (rawKeyContent) => {
|
export async function fetchPlaintext (rawKeyContent) {
|
||||||
const publicKey = await openpgp.readKey({
|
const publicKey = await readKey({
|
||||||
armoredKey: rawKeyContent
|
armoredKey: rawKeyContent
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
throw new Error(`Key could not be read (${error})`)
|
throw new Error(`Key could not be read (${error})`)
|
||||||
})
|
})
|
||||||
|
|
||||||
return publicKey
|
const profile = await parsePublicKey(publicKey)
|
||||||
|
|
||||||
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch a public key using an URI
|
* Fetch a public key using an URI
|
||||||
* @function
|
* @function
|
||||||
* @param {string} uri - URI that defines the location of the key
|
* @param {string} uri - URI that defines the location of the key
|
||||||
* @returns {openpgp.PublicKey}
|
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||||
* @example
|
* @example
|
||||||
* const key1 = doip.keys.fetchURI('hkp:alice@domain.tld');
|
* const key1 = doip.keys.fetchURI('hkp:alice@domain.tld');
|
||||||
* const key2 = doip.keys.fetchURI('hkp:123abc123abc');
|
* const key2 = doip.keys.fetchURI('hkp:123abc123abc');
|
||||||
* const key3 = doip.keys.fetchURI('wkd:alice@domain.tld');
|
* const key3 = doip.keys.fetchURI('wkd:alice@domain.tld');
|
||||||
*/
|
*/
|
||||||
const fetchURI = async (uri) => {
|
export async function fetchURI (uri) {
|
||||||
if (!validUrl.isUri(uri)) {
|
if (!isUri(uri)) {
|
||||||
throw new Error('Invalid URI')
|
throw new Error('Invalid URI')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,115 +230,103 @@ const fetchURI = async (uri) => {
|
||||||
* This function will also try and parse the input as a plaintext key
|
* This function will also try and parse the input as a plaintext key
|
||||||
* @function
|
* @function
|
||||||
* @param {string} identifier - URI that defines the location of the key
|
* @param {string} identifier - URI that defines the location of the key
|
||||||
* @returns {openpgp.PublicKey}
|
* @returns {Promise<Profile>} The profile from the fetched OpenPGP key
|
||||||
* @example
|
* @example
|
||||||
* const key1 = doip.keys.fetch('alice@domain.tld');
|
* const key1 = doip.keys.fetch('alice@domain.tld');
|
||||||
* const key2 = doip.keys.fetch('123abc123abc');
|
* const key2 = doip.keys.fetch('123abc123abc');
|
||||||
*/
|
*/
|
||||||
const fetch = async (identifier) => {
|
export async function fetch (identifier) {
|
||||||
const re = /([a-zA-Z0-9@._=+-]*)(?::([a-zA-Z0-9@._=+-]*))?/
|
const re = /([a-zA-Z0-9@._=+-]*)(?::([a-zA-Z0-9@._=+-]*))?/
|
||||||
const match = identifier.match(re)
|
const match = identifier.match(re)
|
||||||
|
|
||||||
let pubKey = null
|
let profile = null
|
||||||
|
|
||||||
// Attempt plaintext
|
// Attempt plaintext
|
||||||
if (!pubKey) {
|
try {
|
||||||
try {
|
profile = await fetchPlaintext(identifier)
|
||||||
pubKey = await fetchPlaintext(identifier)
|
} catch (e) {}
|
||||||
} catch (e) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Attempt WKD
|
// Attempt WKD
|
||||||
if (!pubKey && identifier.includes('@')) {
|
if (!profile && identifier.includes('@')) {
|
||||||
try {
|
try {
|
||||||
pubKey = await fetchWKD(match[1])
|
profile = await fetchWKD(match[1])
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt HKP
|
// Attempt HKP
|
||||||
if (!pubKey) {
|
if (!profile) {
|
||||||
pubKey = await fetchHKP(
|
profile = await fetchHKP(
|
||||||
match[2] ? match[2] : match[1],
|
match[2] ? match[2] : match[1],
|
||||||
match[2] ? match[1] : null
|
match[2] ? match[1] : null
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pubKey) {
|
if (!profile) {
|
||||||
throw new Error('Key does not exist or could not be fetched')
|
throw new Error('Key does not exist or could not be fetched')
|
||||||
}
|
}
|
||||||
|
|
||||||
return pubKey
|
return profile
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Process a public key to get user data and claims
|
* Process a public key to get a profile
|
||||||
* @function
|
* @function
|
||||||
* @param {openpgp.PublicKey} publicKey - The public key to process
|
* @param {PublicKey} publicKey - The public key to parse
|
||||||
* @returns {object}
|
* @returns {Promise<Profile>} The profile from the processed OpenPGP key
|
||||||
* @example
|
* @example
|
||||||
* const key = doip.keys.fetchURI('hkp:alice@domain.tld');
|
* const key = doip.keys.fetchURI('hkp:alice@domain.tld');
|
||||||
* const data = doip.keys.process(key);
|
* const profile = doip.keys.parsePublicKey(key);
|
||||||
* data.users[0].claims.forEach(claim => {
|
* profile.personas[0].claims.forEach(claim => {
|
||||||
* console.log(claim.uri);
|
* console.log(claim.uri);
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
const process = async (publicKey) => {
|
export async function parsePublicKey (publicKey) {
|
||||||
if (!publicKey || !(publicKey instanceof openpgp.PublicKey)) {
|
if (!(publicKey && (publicKey instanceof PublicKey))) {
|
||||||
throw new Error('Invalid public key')
|
throw new Error('Invalid public key')
|
||||||
}
|
}
|
||||||
|
|
||||||
const fingerprint = publicKey.getFingerprint()
|
const fingerprint = publicKey.getFingerprint()
|
||||||
const primaryUser = await publicKey.getPrimaryUser()
|
const primaryUser = await publicKey.getPrimaryUser()
|
||||||
const users = publicKey.users
|
const users = publicKey.users
|
||||||
const usersOutput = []
|
const personas = []
|
||||||
|
|
||||||
users.forEach((user, i) => {
|
users.forEach((user, i) => {
|
||||||
usersOutput[i] = {
|
if (!user.userID) return
|
||||||
userData: {
|
|
||||||
id: user.userID ? user.userID.userID : null,
|
const pe = new Persona(user.userID.name, [])
|
||||||
name: user.userID ? user.userID.name : null,
|
pe.setIdentifier(user.userID.userID)
|
||||||
email: user.userID ? user.userID.email : null,
|
pe.setDescription(user.userID.comment)
|
||||||
comment: user.userID ? user.userID.comment : null,
|
pe.setEmailAddress(user.userID.email)
|
||||||
isPrimary: primaryUser.index === i,
|
|
||||||
isRevoked: false
|
|
||||||
},
|
|
||||||
claims: []
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('selfCertifications' in user && user.selfCertifications.length > 0) {
|
if ('selfCertifications' in user && user.selfCertifications.length > 0) {
|
||||||
const selfCertification = user.selfCertifications[0]
|
const selfCertification = user.selfCertifications.sort((e1, e2) => e2.created.getTime() - e1.created.getTime())[0]
|
||||||
|
|
||||||
|
if (selfCertification.revoked) {
|
||||||
|
pe.revoke()
|
||||||
|
}
|
||||||
const notations = selfCertification.rawNotations
|
const notations = selfCertification.rawNotations
|
||||||
usersOutput[i].claims = notations
|
pe.claims = notations
|
||||||
.filter(
|
.filter(
|
||||||
({ name, humanReadable }) =>
|
({ name, humanReadable }) =>
|
||||||
humanReadable && (name === 'proof@ariadne.id' || name === 'proof@metacode.biz')
|
humanReadable && (name === 'proof@ariadne.id' || name === 'proof@metacode.biz')
|
||||||
)
|
)
|
||||||
.map(
|
.map(
|
||||||
({ value }) =>
|
({ value }) =>
|
||||||
new Claim(new TextDecoder().decode(value), fingerprint)
|
new Claim(new TextDecoder().decode(value), `openpgp4fpr:${fingerprint}`)
|
||||||
)
|
)
|
||||||
|
|
||||||
usersOutput[i].userData.isRevoked = selfCertification.revoked
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
personas.push(pe)
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
const profile = new Profile(ProfileType.OPENPGP, `openpgp4fpr:${fingerprint}`, personas)
|
||||||
fingerprint: fingerprint,
|
profile.primaryPersonaIndex = primaryUser.index
|
||||||
users: usersOutput,
|
|
||||||
primaryUserIndex: primaryUser.index,
|
|
||||||
key: {
|
|
||||||
data: publicKey,
|
|
||||||
fetchMethod: null,
|
|
||||||
uri: null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
exports.fetchHKP = fetchHKP
|
profile.publicKey.keyType = PublicKeyType.OPENPGP
|
||||||
exports.fetchWKD = fetchWKD
|
profile.publicKey.fingerprint = fingerprint
|
||||||
exports.fetchKeybase = fetchKeybase
|
profile.publicKey.encoding = PublicKeyEncoding.ARMORED_PGP
|
||||||
exports.fetchPlaintext = fetchPlaintext
|
profile.publicKey.encodedKey = publicKey.armor()
|
||||||
exports.fetchURI = fetchURI
|
profile.publicKey.key = publicKey
|
||||||
exports.fetch = fetch
|
|
||||||
exports.process = process
|
return profile
|
||||||
|
}
|
203
src/persona.js
Normal file
203
src/persona.js
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
/*
|
||||||
|
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 { Claim } from './claim.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class
|
||||||
|
* @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 - Name of the persona
|
||||||
|
* @param {Array<Claim>} claims - Claims of the persona
|
||||||
|
*/
|
||||||
|
constructor (name, claims) {
|
||||||
|
/**
|
||||||
|
* Identifier of the persona
|
||||||
|
* @type {string | null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.identifier = null
|
||||||
|
/**
|
||||||
|
* Name to be displayed on the profile page
|
||||||
|
* @type {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.name = name
|
||||||
|
/**
|
||||||
|
* Email address of the persona
|
||||||
|
* @type {string | null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.email = null
|
||||||
|
/**
|
||||||
|
* Description to be displayed on the profile page
|
||||||
|
* @type {string | null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.description = null
|
||||||
|
/**
|
||||||
|
* URL to an avatar image
|
||||||
|
* @type {string | null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.avatarUrl = null
|
||||||
|
/**
|
||||||
|
* Theme color
|
||||||
|
* @type {string | null}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.themeColor = null
|
||||||
|
/**
|
||||||
|
* List of identity claims
|
||||||
|
* @type {Array<Claim>}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.claims = claims
|
||||||
|
/**
|
||||||
|
* Has the persona been revoked
|
||||||
|
* @type {boolean}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.isRevoked = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a JSON object and convert it into a persona
|
||||||
|
* @function
|
||||||
|
* @param {object} personaObject - JSON representation of a persona
|
||||||
|
* @param {number} profileVersion - Version of the Profile containing the persona
|
||||||
|
* @returns {Persona | Error} Parsed persona
|
||||||
|
* @example
|
||||||
|
* doip.Persona.fromJSON(JSON.stringify(persona), 2);
|
||||||
|
*/
|
||||||
|
static fromJSON (personaObject, profileVersion) {
|
||||||
|
/** @type {Persona} */
|
||||||
|
let persona
|
||||||
|
let result
|
||||||
|
|
||||||
|
if (typeof personaObject === 'object' && profileVersion) {
|
||||||
|
switch (profileVersion) {
|
||||||
|
case 2:
|
||||||
|
result = importJsonPersonaVersion2(personaObject)
|
||||||
|
if (result instanceof Error) {
|
||||||
|
throw result
|
||||||
|
}
|
||||||
|
persona = result
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid persona version')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return persona
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the persona's identifier
|
||||||
|
* @function
|
||||||
|
* @param {string} identifier - Identifier of the persona
|
||||||
|
*/
|
||||||
|
setIdentifier (identifier) {
|
||||||
|
this.identifier = identifier
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the persona's description
|
||||||
|
* @function
|
||||||
|
* @param {string} description - Description of the persona
|
||||||
|
*/
|
||||||
|
setDescription (description) {
|
||||||
|
this.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the persona's email address
|
||||||
|
* @function
|
||||||
|
* @param {string} email - Email address of the persona
|
||||||
|
*/
|
||||||
|
setEmailAddress (email) {
|
||||||
|
this.email = email
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the URL to the persona's avatar
|
||||||
|
* @function
|
||||||
|
* @param {string} avatarUrl - URL to the persona's avatar
|
||||||
|
*/
|
||||||
|
setAvatarUrl (avatarUrl) {
|
||||||
|
this.avatarUrl = avatarUrl
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a claim
|
||||||
|
* @function
|
||||||
|
* @param {Claim} claim - Claim to add
|
||||||
|
*/
|
||||||
|
addClaim (claim) {
|
||||||
|
this.claims.push(claim)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Revoke the persona
|
||||||
|
* @function
|
||||||
|
*/
|
||||||
|
revoke () {
|
||||||
|
this.isRevoked = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a JSON representation of the persona
|
||||||
|
* @function
|
||||||
|
* @returns {object} JSON representation of the persona
|
||||||
|
*/
|
||||||
|
toJSON () {
|
||||||
|
return {
|
||||||
|
identifier: this.identifier,
|
||||||
|
name: this.name,
|
||||||
|
email: this.email,
|
||||||
|
description: this.description,
|
||||||
|
avatarUrl: this.avatarUrl,
|
||||||
|
themeColor: this.themeColor,
|
||||||
|
isRevoked: this.isRevoked,
|
||||||
|
claims: this.claims.map(x => x.toJSON())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
}
|
176
src/profile.js
Normal file
176
src/profile.js
Normal file
|
@ -0,0 +1,176 @@
|
||||||
|
/*
|
||||||
|
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 { PublicKeyFetchMethod, PublicKeyEncoding, PublicKeyType, ProfileType } from './enums.js'
|
||||||
|
import { Persona } from './persona.js'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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]);
|
||||||
|
* const profile = Profile([pers]);
|
||||||
|
*/
|
||||||
|
export class Profile {
|
||||||
|
/**
|
||||||
|
* Create a new profile
|
||||||
|
* @function
|
||||||
|
* @param {ProfileType} profileType - Type of profile (ASP, OpenPGP, etc.)
|
||||||
|
* @param {string} identifier - Profile identifier (fingerprint, URI, etc.)
|
||||||
|
* @param {Array<Persona>} personas - Personas of the profile
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
constructor (profileType, identifier, personas) {
|
||||||
|
this.profileVersion = 2
|
||||||
|
/**
|
||||||
|
* Profile version
|
||||||
|
* @type {ProfileType}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.profileType = profileType
|
||||||
|
/**
|
||||||
|
* Identifier of the profile (fingerprint, email address, uri...)
|
||||||
|
* @type {string}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.identifier = identifier
|
||||||
|
/**
|
||||||
|
* List of personas
|
||||||
|
* @type {Array<Persona>}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.personas = personas || []
|
||||||
|
/**
|
||||||
|
* Index of primary persona (to be displayed first or prominently)
|
||||||
|
* @type {number}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.primaryPersonaIndex = personas.length > 0 ? 0 : -1
|
||||||
|
/**
|
||||||
|
* The cryptographic key associated with the profile
|
||||||
|
* @type {import('./types').ProfilePublicKey}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.publicKey = {
|
||||||
|
keyType: PublicKeyType.NONE,
|
||||||
|
fingerprint: null,
|
||||||
|
encoding: PublicKeyEncoding.NONE,
|
||||||
|
encodedKey: null,
|
||||||
|
key: null,
|
||||||
|
fetch: {
|
||||||
|
method: PublicKeyFetchMethod.NONE,
|
||||||
|
query: null,
|
||||||
|
resolvedUrl: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* List of verifier URLs
|
||||||
|
* @type {Array<import('./types').ProfileVerifier>}
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
this.verifiers = []
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse a JSON object and convert it into a profile
|
||||||
|
* @function
|
||||||
|
* @param {object} profileObject - JSON representation of a profile
|
||||||
|
* @returns {Profile | Error} Parsed profile
|
||||||
|
* @example
|
||||||
|
* doip.Profile.fromJSON(JSON.stringify(profile));
|
||||||
|
*/
|
||||||
|
static fromJSON (profileObject) {
|
||||||
|
/** @type {Profile} */
|
||||||
|
let profile
|
||||||
|
let result
|
||||||
|
|
||||||
|
if (typeof profileObject === 'object' && 'profileVersion' in profileObject) {
|
||||||
|
switch (profileObject.profileVersion) {
|
||||||
|
case 2:
|
||||||
|
result = importJsonProfileVersion2(profileObject)
|
||||||
|
if (result instanceof Error) {
|
||||||
|
throw result
|
||||||
|
}
|
||||||
|
profile = result
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
throw new Error('Invalid profile version')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add profile verifier to the profile
|
||||||
|
* @function
|
||||||
|
* @param {string} name - Name of the verifier
|
||||||
|
* @param {string} url - URL of the verifier
|
||||||
|
*/
|
||||||
|
addVerifier (name, url) {
|
||||||
|
this.verifiers.push({ name, url })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a JSON representation of the profile
|
||||||
|
* @function
|
||||||
|
* @returns {object} JSON representation of the profile
|
||||||
|
*/
|
||||||
|
toJSON () {
|
||||||
|
return {
|
||||||
|
profileVersion: this.profileVersion,
|
||||||
|
profileType: this.profileType,
|
||||||
|
identifier: this.identifier,
|
||||||
|
personas: this.personas.map(x => x.toJSON()),
|
||||||
|
primaryPersonaIndex: this.primaryPersonaIndex,
|
||||||
|
publicKey: {
|
||||||
|
keyType: this.publicKey.keyType,
|
||||||
|
fingerprint: this.publicKey.fingerprint,
|
||||||
|
encoding: this.publicKey.encoding,
|
||||||
|
encodedKey: this.publicKey.encodedKey,
|
||||||
|
fetch: {
|
||||||
|
method: this.publicKey.fetch.method,
|
||||||
|
query: this.publicKey.fetch.query,
|
||||||
|
resolvedUrl: this.publicKey.fetch.resolvedUrl
|
||||||
|
}
|
||||||
|
},
|
||||||
|
verifiers: this.verifiers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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
|
||||||
|
}
|
101
src/proofs.js
101
src/proofs.js
|
@ -13,10 +13,11 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const jsEnv = require('browser-or-node')
|
import { isNode } from 'browser-or-node'
|
||||||
const fetcher = require('./fetcher')
|
import { fetcher } from './index.js'
|
||||||
const utils = require('./utils')
|
import { generateProxyURL } from './utils.js'
|
||||||
const E = require('./enums')
|
import { ProxyPolicy, ProofAccessRestriction } from './enums.js'
|
||||||
|
import { ServiceProvider } from './serviceProvider.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @module proofs
|
* @module proofs
|
||||||
|
@ -28,40 +29,35 @@ const E = require('./enums')
|
||||||
* the `data` parameter and the proxy policy set in the `opts` parameter to
|
* 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
|
* choose the right approach to fetch the proof. An error will be thrown if no
|
||||||
* approach is possible.
|
* approach is possible.
|
||||||
* @async
|
* @param {ServiceProvider} data - Data from a claim definition
|
||||||
* @param {object} data - Data from a claim definition
|
* @param {import('./types').VerificationConfig} opts - Options to enable the request
|
||||||
* @param {object} opts - Options to enable the request
|
* @returns {Promise<object|string>} Fetched proof data
|
||||||
* @returns {Promise<object|string>}
|
|
||||||
*/
|
*/
|
||||||
const fetch = (data, opts) => {
|
export async function fetch (data, opts) {
|
||||||
switch (data.proof.request.fetcher) {
|
if (isNode) {
|
||||||
case E.Fetcher.HTTP:
|
|
||||||
data.proof.request.data.format = data.proof.request.format
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if (jsEnv.isNode) {
|
|
||||||
return handleNodeRequests(data, opts)
|
return handleNodeRequests(data, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
return handleBrowserRequests(data, opts)
|
return handleBrowserRequests(data, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ServiceProvider} data - Data from a claim definition
|
||||||
|
* @param {object} opts - Options to enable the request
|
||||||
|
* @returns {Promise<object|string>} Fetched proof data
|
||||||
|
*/
|
||||||
const handleBrowserRequests = (data, opts) => {
|
const handleBrowserRequests = (data, opts) => {
|
||||||
switch (opts.proxy.policy) {
|
switch (opts.proxy.policy) {
|
||||||
case E.ProxyPolicy.ALWAYS:
|
case ProxyPolicy.ALWAYS:
|
||||||
return createProxyRequestPromise(data, opts)
|
return createProxyRequestPromise(data, opts)
|
||||||
|
|
||||||
case E.ProxyPolicy.NEVER:
|
case ProxyPolicy.NEVER:
|
||||||
switch (data.proof.request.access) {
|
switch (data.proof.request.accessRestriction) {
|
||||||
case E.ProofAccess.GENERIC:
|
case ProofAccessRestriction.NONE:
|
||||||
case E.ProofAccess.GRANTED:
|
case ProofAccessRestriction.GRANTED:
|
||||||
return createDefaultRequestPromise(data, opts)
|
return createDefaultRequestPromise(data, opts)
|
||||||
case E.ProofAccess.NOCORS:
|
case ProofAccessRestriction.NOCORS:
|
||||||
case E.ProofAccess.SERVER:
|
case ProofAccessRestriction.SERVER:
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Impossible to fetch proof (bad combination of service access and proxy policy)'
|
'Impossible to fetch proof (bad combination of service access and proxy policy)'
|
||||||
)
|
)
|
||||||
|
@ -69,15 +65,15 @@ const handleBrowserRequests = (data, opts) => {
|
||||||
throw new Error('Invalid proof access value')
|
throw new Error('Invalid proof access value')
|
||||||
}
|
}
|
||||||
|
|
||||||
case E.ProxyPolicy.ADAPTIVE:
|
case ProxyPolicy.ADAPTIVE:
|
||||||
switch (data.proof.request.access) {
|
switch (data.proof.request.accessRestriction) {
|
||||||
case E.ProofAccess.GENERIC:
|
case ProofAccessRestriction.NONE:
|
||||||
return createFallbackRequestPromise(data, opts)
|
return createFallbackRequestPromise(data, opts)
|
||||||
case E.ProofAccess.NOCORS:
|
case ProofAccessRestriction.NOCORS:
|
||||||
return createProxyRequestPromise(data, opts)
|
return createProxyRequestPromise(data, opts)
|
||||||
case E.ProofAccess.GRANTED:
|
case ProofAccessRestriction.GRANTED:
|
||||||
return createFallbackRequestPromise(data, opts)
|
return createFallbackRequestPromise(data, opts)
|
||||||
case E.ProofAccess.SERVER:
|
case ProofAccessRestriction.SERVER:
|
||||||
return createProxyRequestPromise(data, opts)
|
return createProxyRequestPromise(data, opts)
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid proof access value')
|
throw new Error('Invalid proof access value')
|
||||||
|
@ -88,15 +84,20 @@ const handleBrowserRequests = (data, opts) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ServiceProvider} data - Data from a claim definition
|
||||||
|
* @param {object} opts - Options to enable the request
|
||||||
|
* @returns {Promise<object|string>} Fetched proof data
|
||||||
|
*/
|
||||||
const handleNodeRequests = (data, opts) => {
|
const handleNodeRequests = (data, opts) => {
|
||||||
switch (opts.proxy.policy) {
|
switch (opts.proxy.policy) {
|
||||||
case E.ProxyPolicy.ALWAYS:
|
case ProxyPolicy.ALWAYS:
|
||||||
return createProxyRequestPromise(data, opts)
|
return createProxyRequestPromise(data, opts)
|
||||||
|
|
||||||
case E.ProxyPolicy.NEVER:
|
case ProxyPolicy.NEVER:
|
||||||
return createDefaultRequestPromise(data, opts)
|
return createDefaultRequestPromise(data, opts)
|
||||||
|
|
||||||
case E.ProxyPolicy.ADAPTIVE:
|
case ProxyPolicy.ADAPTIVE:
|
||||||
return createFallbackRequestPromise(data, opts)
|
return createFallbackRequestPromise(data, opts)
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -104,14 +105,22 @@ const handleNodeRequests = (data, opts) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ServiceProvider} data - Data from a claim definition
|
||||||
|
* @param {object} opts - Options to enable the request
|
||||||
|
* @returns {Promise<object|string>} Fetched proof data
|
||||||
|
*/
|
||||||
const createDefaultRequestPromise = (data, opts) => {
|
const createDefaultRequestPromise = (data, opts) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!(data.proof.request.fetcher in fetcher)) {
|
||||||
|
reject(new Error(`fetcher for ${data.proof.request.fetcher} not found`))
|
||||||
|
}
|
||||||
fetcher[data.proof.request.fetcher]
|
fetcher[data.proof.request.fetcher]
|
||||||
.fn(data.proof.request.data, opts)
|
.fn(data.proof.request.data, opts)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
return resolve({
|
return resolve({
|
||||||
fetcher: data.proof.request.fetcher,
|
fetcher: data.proof.request.fetcher,
|
||||||
data: data,
|
data,
|
||||||
viaProxy: false,
|
viaProxy: false,
|
||||||
result: res
|
result: res
|
||||||
})
|
})
|
||||||
|
@ -122,11 +131,16 @@ const createDefaultRequestPromise = (data, opts) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ServiceProvider} data - Data from a claim definition
|
||||||
|
* @param {object} opts - Options to enable the request
|
||||||
|
* @returns {Promise<object|string>} Fetched proof data
|
||||||
|
*/
|
||||||
const createProxyRequestPromise = (data, opts) => {
|
const createProxyRequestPromise = (data, opts) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
let proxyUrl
|
let proxyUrl
|
||||||
try {
|
try {
|
||||||
proxyUrl = utils.generateProxyURL(
|
proxyUrl = generateProxyURL(
|
||||||
data.proof.request.fetcher,
|
data.proof.request.fetcher,
|
||||||
data.proof.request.data,
|
data.proof.request.data,
|
||||||
opts
|
opts
|
||||||
|
@ -137,15 +151,15 @@ const createProxyRequestPromise = (data, opts) => {
|
||||||
|
|
||||||
const requestData = {
|
const requestData = {
|
||||||
url: proxyUrl,
|
url: proxyUrl,
|
||||||
format: data.proof.request.format,
|
format: data.proof.response.format,
|
||||||
fetcherTimeout: fetcher[data.proof.request.fetcher].timeout
|
fetcherTimeout: data.proof.request.fetcher in fetcher ? fetcher[data.proof.request.fetcher].timeout : 30000
|
||||||
}
|
}
|
||||||
fetcher.http
|
fetcher.http
|
||||||
.fn(requestData, opts)
|
.fn(requestData, opts)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
return resolve({
|
return resolve({
|
||||||
fetcher: 'http',
|
fetcher: 'http',
|
||||||
data: data,
|
data,
|
||||||
viaProxy: true,
|
viaProxy: true,
|
||||||
result: res
|
result: res
|
||||||
})
|
})
|
||||||
|
@ -156,6 +170,11 @@ const createProxyRequestPromise = (data, opts) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {ServiceProvider} data - Data from a claim definition
|
||||||
|
* @param {object} opts - Options to enable the request
|
||||||
|
* @returns {Promise<object|string>} Fetched proof data
|
||||||
|
*/
|
||||||
const createFallbackRequestPromise = (data, opts) => {
|
const createFallbackRequestPromise = (data, opts) => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
createDefaultRequestPromise(data, opts)
|
createDefaultRequestPromise(data, opts)
|
||||||
|
@ -173,5 +192,3 @@ const createFallbackRequestPromise = (data, opts) => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.fetch = fetch
|
|
||||||
|
|
|
@ -1,337 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const router = require('express').Router()
|
|
||||||
const dns = require('dns')
|
|
||||||
const axios = require('axios')
|
|
||||||
const validUrl = require('valid-url')
|
|
||||||
const jsdom = require('jsdom')
|
|
||||||
const { client, xml } = require('@xmpp/client')
|
|
||||||
const debug = require('@xmpp/debug')
|
|
||||||
const irc = require('irc-upd')
|
|
||||||
require('dotenv').config()
|
|
||||||
|
|
||||||
const xmppService = process.env.XMPP_SERVICE || null
|
|
||||||
const xmppUsername = process.env.XMPP_USERNAME || null
|
|
||||||
const xmppPassword = process.env.XMPP_PASSWORD || null
|
|
||||||
const twitterBearerToken = process.env.TWITTER_BEARER_TOKEN || null
|
|
||||||
const matrixInstance = process.env.MATRIX_INSTANCE || null
|
|
||||||
const matrixAccessToken = process.env.MATRIX_ACCESS_TOKEN || null
|
|
||||||
const ircNick = process.env.IRC_NICK || null
|
|
||||||
|
|
||||||
let xmpp = null
|
|
||||||
let iqCaller = null
|
|
||||||
let xmppEnabled = true
|
|
||||||
let twitterEnabled = false
|
|
||||||
let matrixEnabled = false
|
|
||||||
let ircEnabled = false
|
|
||||||
|
|
||||||
if (!xmppService || !xmppUsername || !xmppPassword) {
|
|
||||||
xmppEnabled = false
|
|
||||||
}
|
|
||||||
if (twitterBearerToken) {
|
|
||||||
twitterEnabled = true
|
|
||||||
}
|
|
||||||
if (matrixInstance && matrixAccessToken) {
|
|
||||||
matrixEnabled = true
|
|
||||||
}
|
|
||||||
if (ircNick) {
|
|
||||||
ircEnabled = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const xmppStart = async (xmppService, xmppUsername, xmppPassword) => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
const xmpp = client({
|
|
||||||
service: xmppService,
|
|
||||||
username: xmppUsername,
|
|
||||||
password: xmppPassword
|
|
||||||
})
|
|
||||||
if (process.env.NODE_ENV !== 'production') {
|
|
||||||
debug(xmpp, true)
|
|
||||||
}
|
|
||||||
const { iqCaller } = xmpp
|
|
||||||
xmpp.start()
|
|
||||||
xmpp.on('online', (address) => {
|
|
||||||
console.log('online', address.toString())
|
|
||||||
resolve({ xmpp: xmpp, iqCaller: iqCaller })
|
|
||||||
})
|
|
||||||
xmpp.on('error', (error) => {
|
|
||||||
reject(error)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
router.get('/', async (req, res) => {
|
|
||||||
res.status(200).json({
|
|
||||||
message:
|
|
||||||
'Available endpoints: /json/:url, /text/:url, /dns/:hostname, /xmpp/:xmppid, /twitter/:tweetid, /matrix/:roomid/:eventid, /irc/:ircserver/:ircnick'
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
router.param('url', async (req, res, next, url) => {
|
|
||||||
req.params.url = decodeURI(url)
|
|
||||||
|
|
||||||
if (!validUrl.isUri(req.params.url)) {
|
|
||||||
return res.status(400).send({ message: 'URL provided was not valid' })
|
|
||||||
}
|
|
||||||
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
|
|
||||||
router.param('xmppid', async (req, res, next, xmppid) => {
|
|
||||||
req.params.xmppid = xmppid
|
|
||||||
|
|
||||||
if (/^\w+([.-]?\w+)*@\w+([.-]?\w+)*(\.\w{2,})+$/.test(req.params.xmppid)) {
|
|
||||||
next()
|
|
||||||
} else {
|
|
||||||
return res.status(400).json({ message: 'XMPP_ID was not valid' })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
router.param('xmppdata', async (req, res, next, xmppdata) => {
|
|
||||||
req.params.xmppdata = xmppdata.toUpperCase()
|
|
||||||
|
|
||||||
const allowedData = [
|
|
||||||
'FN',
|
|
||||||
'NUMBER',
|
|
||||||
'USERID',
|
|
||||||
'URL',
|
|
||||||
'BDAY',
|
|
||||||
'NICKNAME',
|
|
||||||
'NOTE',
|
|
||||||
'DESC'
|
|
||||||
]
|
|
||||||
|
|
||||||
if (!allowedData.includes(req.params.xmppdata)) {
|
|
||||||
return res.status(400).json({
|
|
||||||
message:
|
|
||||||
'Allowed data are: FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
next()
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/get/json/:url', (req, res) => {
|
|
||||||
axios.get(req.params.url,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
return result.data
|
|
||||||
})
|
|
||||||
.then(async (result) => {
|
|
||||||
return res.status(200).json({ url: req.params.url, content: result })
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
return res.status(400).send({ error: e })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/get/text/:url', (req, res) => {
|
|
||||||
axios.get(req.params.url,
|
|
||||||
{
|
|
||||||
responseType: 'text'
|
|
||||||
})
|
|
||||||
.then(result => {
|
|
||||||
return result.data
|
|
||||||
})
|
|
||||||
.then(async (result) => {
|
|
||||||
return res.status(200).json({ url: req.params.url, content: result })
|
|
||||||
})
|
|
||||||
.catch((e) => {
|
|
||||||
return res.status(400).send({ error: e })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/get/dns/:hostname', async (req, res) => {
|
|
||||||
dns.resolveTxt(req.params.hostname, (err, records) => {
|
|
||||||
if (err) {
|
|
||||||
throw new Error(err)
|
|
||||||
}
|
|
||||||
const out = {
|
|
||||||
hostname: req.params.hostname,
|
|
||||||
records: {
|
|
||||||
txt: records
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res.status(200).json(out)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/get/xmpp/:xmppid', async (req, res) => {
|
|
||||||
return res
|
|
||||||
.status(400)
|
|
||||||
.json(
|
|
||||||
'Data request parameter missing (FN, NUMBER, USERID, URL, BDAY, NICKNAME, NOTE, DESC)'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/get/xmpp/:xmppid/:xmppdata', async (req, res) => {
|
|
||||||
if (!xmppEnabled) {
|
|
||||||
return res.status(500).json('XMPP not enabled on server')
|
|
||||||
}
|
|
||||||
if (!xmpp) {
|
|
||||||
const xmppStartRes = await xmppStart(
|
|
||||||
xmppService,
|
|
||||||
xmppUsername,
|
|
||||||
xmppPassword
|
|
||||||
)
|
|
||||||
xmpp = xmppStartRes.xmpp
|
|
||||||
iqCaller = xmppStartRes.iqCaller
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await iqCaller.request(
|
|
||||||
xml(
|
|
||||||
'iq',
|
|
||||||
{ type: 'get', to: req.params.xmppid },
|
|
||||||
xml('vCard', 'vcard-temp')
|
|
||||||
),
|
|
||||||
30 * 1000
|
|
||||||
)
|
|
||||||
|
|
||||||
const vcardRow = response.getChild('vCard', 'vcard-temp').toString()
|
|
||||||
|
|
||||||
const dom = new jsdom.JSDOM(vcardRow)
|
|
||||||
|
|
||||||
try {
|
|
||||||
let vcard
|
|
||||||
|
|
||||||
switch (req.params.xmppdata.toLowerCase()) {
|
|
||||||
case 'desc':
|
|
||||||
case 'note':
|
|
||||||
vcard = dom.window.document.querySelector('note text')
|
|
||||||
if (!vcard) {
|
|
||||||
vcard = dom.window.document.querySelector('DESC')
|
|
||||||
}
|
|
||||||
if (vcard) {
|
|
||||||
vcard = vcard.textContent
|
|
||||||
} else {
|
|
||||||
throw new Error('No DESC or NOTE field found in vCard')
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
vcard = dom.window.document.querySelector(req.params.xmppdata)
|
|
||||||
.textContent
|
|
||||||
break
|
|
||||||
}
|
|
||||||
return res.status(200).json(vcard)
|
|
||||||
} catch (error) {
|
|
||||||
return res
|
|
||||||
.status(400)
|
|
||||||
.json({ message: 'Request could not be fulfilled', error: error })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/get/twitter/:tweetid', async (req, res) => {
|
|
||||||
if (!twitterEnabled) {
|
|
||||||
return res.status(500).json('Twitter not enabled on server')
|
|
||||||
}
|
|
||||||
|
|
||||||
axios.get(
|
|
||||||
`https://api.twitter.com/1.1/statuses/show.json?id=${req.params.tweetid}`,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json',
|
|
||||||
Authorization: `Bearer ${twitterBearerToken}`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
.then(data => {
|
|
||||||
return data.data
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).json({ data: data, message: 'Success', error: {} })
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
return res.status(error.statusCode || 400).json({
|
|
||||||
data: [],
|
|
||||||
message: 'Request could not be fulfilled',
|
|
||||||
error: error
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/get/matrix/:matrixroomid/:matrixeventid', async (req, res) => {
|
|
||||||
if (!matrixEnabled) {
|
|
||||||
return res.status(500).json('Matrix not enabled on server')
|
|
||||||
}
|
|
||||||
|
|
||||||
const url = `https://${matrixInstance}/_matrix/client/r0/rooms/${req.params.matrixroomid}/event/${req.params.matrixeventid}?access_token=${matrixAccessToken}`
|
|
||||||
|
|
||||||
axios.get(url,
|
|
||||||
{
|
|
||||||
headers: {
|
|
||||||
Accept: 'application/json'
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.then(data => {
|
|
||||||
return data.data
|
|
||||||
})
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).json({ data: data, message: 'Success', error: {} })
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
return res.status(error.statusCode || 400).json({
|
|
||||||
data: [],
|
|
||||||
message: 'Request could not be fulfilled',
|
|
||||||
error: error
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
router.get('/get/irc/:ircserver/:ircnick', async (req, res) => {
|
|
||||||
if (!ircEnabled) {
|
|
||||||
return res.status(500).json('IRC not enabled on server')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const client = new irc.Client(req.params.ircserver, ircNick, {
|
|
||||||
port: 6697,
|
|
||||||
secure: true,
|
|
||||||
channels: []
|
|
||||||
})
|
|
||||||
const reKey = /[a-zA-Z0-9\-_]+\s+:\s(openpgp4fpr:.*)/
|
|
||||||
const reEnd = /End\sof\s.*\staxonomy./
|
|
||||||
const keys = []
|
|
||||||
|
|
||||||
client.addListener('registered', (message) => {
|
|
||||||
client.send(`PRIVMSG NickServ :TAXONOMY ${req.params.ircnick}`)
|
|
||||||
})
|
|
||||||
client.addListener('notice', (nick, to, text, message) => {
|
|
||||||
if (reKey.test(text)) {
|
|
||||||
const match = text.match(reKey)
|
|
||||||
keys.push(match[1])
|
|
||||||
}
|
|
||||||
if (reEnd.test(text)) {
|
|
||||||
client.disconnect()
|
|
||||||
return res
|
|
||||||
.status(200)
|
|
||||||
.json({ data: keys, message: 'Success', error: {} })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
return res.status(400).json({
|
|
||||||
data: [],
|
|
||||||
message: 'Request could not be fulfilled',
|
|
||||||
error: error
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
module.exports = router
|
|
|
@ -1,249 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
const router = require('express').Router()
|
|
||||||
const { query, validationResult } = require('express-validator')
|
|
||||||
const fetcher = require('../../../fetcher')
|
|
||||||
const E = require('../../../enums')
|
|
||||||
require('dotenv').config()
|
|
||||||
|
|
||||||
const opts = {
|
|
||||||
claims: {
|
|
||||||
irc: {
|
|
||||||
nick: process.env.IRC_NICK || null
|
|
||||||
},
|
|
||||||
matrix: {
|
|
||||||
instance: process.env.MATRIX_INSTANCE || null,
|
|
||||||
accessToken: process.env.MATRIX_ACCESS_TOKEN || null
|
|
||||||
},
|
|
||||||
telegram: {
|
|
||||||
token: process.env.TELEGRAM_TOKEN || null
|
|
||||||
},
|
|
||||||
xmpp: {
|
|
||||||
service: process.env.XMPP_SERVICE || null,
|
|
||||||
username: process.env.XMPP_USERNAME || null,
|
|
||||||
password: process.env.XMPP_PASSWORD || null
|
|
||||||
},
|
|
||||||
twitter: {
|
|
||||||
bearerToken: process.env.TWITTER_BEARER_TOKEN || null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Root route
|
|
||||||
router.get('/', async (req, res) => {
|
|
||||||
return res.status(400).json({ errors: 'Invalid endpoint' })
|
|
||||||
})
|
|
||||||
|
|
||||||
// HTTP route
|
|
||||||
router.get(
|
|
||||||
'/get/http',
|
|
||||||
query('url').isURL(),
|
|
||||||
query('format').isIn([E.ProofFormat.JSON, E.ProofFormat.TEXT]),
|
|
||||||
(req, res) => {
|
|
||||||
const errors = validationResult(req)
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher.http
|
|
||||||
.fn(req.query, opts)
|
|
||||||
.then((result) => {
|
|
||||||
switch (req.query.format) {
|
|
||||||
case E.ProofFormat.JSON:
|
|
||||||
return res.status(200).json(result)
|
|
||||||
|
|
||||||
case E.ProofFormat.TEXT:
|
|
||||||
return res.status(200).send(result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return res.status(400).json({ errors: err.message ? err.message : err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// DNS route
|
|
||||||
router.get('/get/dns', query('domain').isFQDN(), (req, res) => {
|
|
||||||
const errors = validationResult(req)
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher.dns
|
|
||||||
.fn(req.query, opts)
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).send(data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return res.status(400).json({ errors: err.message ? err.message : err })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// XMPP route
|
|
||||||
router.get(
|
|
||||||
'/get/xmpp',
|
|
||||||
query('id').isEmail(),
|
|
||||||
query('field').isIn([
|
|
||||||
'fn',
|
|
||||||
'number',
|
|
||||||
'userid',
|
|
||||||
'url',
|
|
||||||
'bday',
|
|
||||||
'nickname',
|
|
||||||
'note',
|
|
||||||
'desc'
|
|
||||||
]),
|
|
||||||
async (req, res) => {
|
|
||||||
if (
|
|
||||||
!opts.claims.xmpp.service ||
|
|
||||||
!opts.claims.xmpp.username ||
|
|
||||||
!opts.claims.xmpp.password
|
|
||||||
) {
|
|
||||||
return res.status(501).json({ errors: 'XMPP not enabled on server' })
|
|
||||||
}
|
|
||||||
const errors = validationResult(req)
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher.xmpp
|
|
||||||
.fn(req.query, opts)
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).send(data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return res.status(400).json({ errors: err.message ? err.message : err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Twitter route
|
|
||||||
router.get('/get/twitter', query('tweetId').isInt(), async (req, res) => {
|
|
||||||
if (!opts.claims.twitter.bearerToken) {
|
|
||||||
return res.status(501).json({ errors: 'Twitter not enabled on server' })
|
|
||||||
}
|
|
||||||
const errors = validationResult(req)
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher.twitter
|
|
||||||
.fn(req.query, opts)
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).send(data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return res.status(400).json({ errors: err.message ? err.message : err })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Matrix route
|
|
||||||
router.get(
|
|
||||||
'/get/matrix',
|
|
||||||
query('roomId').isString(),
|
|
||||||
query('eventId').isString(),
|
|
||||||
async (req, res) => {
|
|
||||||
if (!opts.claims.matrix.instance || !opts.claims.matrix.accessToken) {
|
|
||||||
return res.status(501).json({ errors: 'Matrix not enabled on server' })
|
|
||||||
}
|
|
||||||
const errors = validationResult(req)
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher.matrix
|
|
||||||
.fn(req.query, opts)
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).send(data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return res.status(400).json({ errors: err.message ? err.message : err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Telegram route
|
|
||||||
router.get(
|
|
||||||
'/get/telegram',
|
|
||||||
query('user').isString(),
|
|
||||||
query('chat').isString(),
|
|
||||||
async (req, res) => {
|
|
||||||
if (!opts.claims.telegram.token) {
|
|
||||||
return res.status(501).json({ errors: 'Telegram not enabled on server' })
|
|
||||||
}
|
|
||||||
|
|
||||||
const errors = validationResult(req)
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher.telegram
|
|
||||||
.fn(req.query, opts)
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).send(data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return res.status(400).json({ errors: err.message ? err.message : err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// IRC route
|
|
||||||
router.get('/get/irc', query('nick').isString(), async (req, res) => {
|
|
||||||
if (!opts.claims.irc.nick) {
|
|
||||||
return res.status(501).json({ errors: 'IRC not enabled on server' })
|
|
||||||
}
|
|
||||||
const errors = validationResult(req)
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher.irc
|
|
||||||
.fn(req.query, opts)
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).send(data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return res.status(400).json({ errors: err.message ? err.message : err })
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Gitlab route
|
|
||||||
router.get(
|
|
||||||
'/get/gitlab',
|
|
||||||
query('domain').isFQDN(),
|
|
||||||
query('username').isString(),
|
|
||||||
async (req, res) => {
|
|
||||||
const errors = validationResult(req)
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(400).json({ errors: errors.array() })
|
|
||||||
}
|
|
||||||
|
|
||||||
fetcher.http
|
|
||||||
.fn({
|
|
||||||
url: `https://${req.query.domain}/api/v4/projects/${req.query.username}%2Fgitlab_proof`,
|
|
||||||
format: 'json'
|
|
||||||
}, opts)
|
|
||||||
.then((data) => {
|
|
||||||
return res.status(200).send(data)
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
return res.status(400).json({ errors: err.message ? err.message : err })
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
module.exports = router
|
|
|
@ -1,56 +0,0 @@
|
||||||
/*
|
|
||||||
Copyright 2021 Yarmo Mackenbach
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
Copyright 2020 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.
|
|
||||||
*/
|
|
||||||
const express = require('express')
|
|
||||||
const app = express()
|
|
||||||
const cors = require('cors')
|
|
||||||
require('dotenv').config()
|
|
||||||
|
|
||||||
app.use(cors())
|
|
||||||
app.set('port', process.env.PORT || 3000)
|
|
||||||
|
|
||||||
app.use('/api/1', require('./api/v1/'))
|
|
||||||
app.use('/api/2', require('./api/v2/'))
|
|
||||||
|
|
||||||
app.get('/', (req, res) => {
|
|
||||||
return res.status(200).json({ message: 'Available endpoints: /api' })
|
|
||||||
})
|
|
||||||
app.get('/api', (req, res) => {
|
|
||||||
return res
|
|
||||||
.status(200)
|
|
||||||
.json({ message: 'Available API versions: /api/1, /api/2' })
|
|
||||||
})
|
|
||||||
app.all('*', (req, res) => {
|
|
||||||
return res.status(404).json({ message: 'API endpoint not found' })
|
|
||||||
})
|
|
||||||
|
|
||||||
app.listen(app.get('port'), () => {
|
|
||||||
console.log(`Node server listening at http://localhost:${app.get('port')}`)
|
|
||||||
})
|
|
373
src/schemas.js
Normal file
373
src/schemas.js
Normal 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
|
||||||
|
}
|
62
src/serviceProvider.js
Normal file
62
src/serviceProvider.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A service provider matched to an identity claim
|
||||||
|
* @class
|
||||||
|
* @public
|
||||||
|
*/
|
||||||
|
export class ServiceProvider {
|
||||||
|
/**
|
||||||
|
* @param {import('./types').ServiceProviderObject} serviceProviderObject - JSON representation of a {@link ServiceProvider}
|
||||||
|
*/
|
||||||
|
constructor (serviceProviderObject) {
|
||||||
|
/**
|
||||||
|
* Details about the service provider
|
||||||
|
* @type {import('./types').ServiceProviderAbout}
|
||||||
|
*/
|
||||||
|
this.about = serviceProviderObject.about
|
||||||
|
/**
|
||||||
|
* What the profile would look like if a claim matches this service provider
|
||||||
|
* @type {import('./types').ServiceProviderProfile}
|
||||||
|
*/
|
||||||
|
this.profile = serviceProviderObject.profile
|
||||||
|
/**
|
||||||
|
* Information about the claim matching process
|
||||||
|
* @type {import('./types').ServiceProviderClaim}
|
||||||
|
*/
|
||||||
|
this.claim = serviceProviderObject.claim
|
||||||
|
/**
|
||||||
|
* Information for the proof verification process
|
||||||
|
* @type {import('./types').ServiceProviderProof}
|
||||||
|
*/
|
||||||
|
this.proof = serviceProviderObject.proof
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a JSON representation of the {@link ServiceProvider}
|
||||||
|
* @function
|
||||||
|
* @returns {import('./types').ServiceProviderObject} JSON representation of a {@link ServiceProvider}
|
||||||
|
*/
|
||||||
|
toJSON () {
|
||||||
|
return {
|
||||||
|
about: this.about,
|
||||||
|
profile: this.profile,
|
||||||
|
claim: this.claim,
|
||||||
|
proof: this.proof
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
270
src/serviceProviders/activitypub.js
Normal file
270
src/serviceProviders/activitypub.js
Normal file
|
@ -0,0 +1,270 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* ActivityPub service provider ({@link https://docs.keyoxide.org/service-providers/activitypub/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/activitypub
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.activitypub.processURI('https://domain.example/@alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { fetcher } from '../index.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'activitypub',
|
||||||
|
name: 'ActivityPub',
|
||||||
|
homepage: 'https://activitypub.rocks'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: uri,
|
||||||
|
uri,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString().toString(),
|
||||||
|
uriIsAmbiguous: true
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri,
|
||||||
|
fetcher: E.Fetcher.ACTIVITYPUB,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||||
|
data: {
|
||||||
|
url: uri
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [
|
||||||
|
{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['summary']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['attachment', 'value']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['content']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const functions = {
|
||||||
|
postprocess: async (/** @type {ServiceProvider} */ claimData, proofData, opts) => {
|
||||||
|
switch (proofData.result.type) {
|
||||||
|
case 'Note': {
|
||||||
|
claimData.profile.uri = proofData.result.attributedTo
|
||||||
|
claimData.profile.display = proofData.result.attributedTo
|
||||||
|
const personData = await fetcher.activitypub.fn({ url: proofData.result.attributedTo }, opts)
|
||||||
|
.catch(_ => null)
|
||||||
|
if (personData) {
|
||||||
|
claimData.profile.display = `@${personData.preferredUsername}@${new URL(claimData.proof.request.uri).hostname}`
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'Person':
|
||||||
|
claimData.profile.display = `@${proofData.result.preferredUsername}@${new URL(claimData.proof.request.uri).hostname}`
|
||||||
|
break
|
||||||
|
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to fetch and process the instance's NodeInfo data
|
||||||
|
const nodeinfo = await _processNodeinfo(new URL(claimData.proof.request.uri).hostname)
|
||||||
|
if (nodeinfo) {
|
||||||
|
claimData.about.name = nodeinfo.software.name
|
||||||
|
claimData.about.id = nodeinfo.software.name
|
||||||
|
claimData.about.homepage = nodeinfo.software.homepage
|
||||||
|
}
|
||||||
|
|
||||||
|
return { claimData, proofData }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const _processNodeinfo = async (/** @type {string} */ domain) => {
|
||||||
|
const nodeinfoRef = await fetch(`https://${domain}/.well-known/nodeinfo`)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('HTTP Status was not 200')
|
||||||
|
}
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.catch(_ => {
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!nodeinfoRef) return null
|
||||||
|
|
||||||
|
// NodeInfo version 2.1
|
||||||
|
{
|
||||||
|
const nodeinfo = nodeinfoRef.links.find(x => { return x.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.1' })
|
||||||
|
if (nodeinfo) {
|
||||||
|
return await fetch(nodeinfo.href)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('HTTP Status was not 200')
|
||||||
|
}
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return {
|
||||||
|
software: {
|
||||||
|
name: res.software.name,
|
||||||
|
version: res.software.version,
|
||||||
|
homepage: res.software.homepage || 'https://activitypub.rocks'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(_ => {
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NodeInfo version 2.0
|
||||||
|
{
|
||||||
|
const nodeinfo = nodeinfoRef.links.find(x => { return x.rel === 'http://nodeinfo.diaspora.software/ns/schema/2.0' })
|
||||||
|
if (nodeinfo) {
|
||||||
|
return await fetch(nodeinfo.href)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('HTTP Status was not 200')
|
||||||
|
}
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return {
|
||||||
|
software: {
|
||||||
|
name: res.software.name,
|
||||||
|
version: res.software.version,
|
||||||
|
homepage: 'https://activitypub.rocks'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(_ => {
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NodeInfo version 1.1
|
||||||
|
{
|
||||||
|
const nodeinfo = nodeinfoRef.links.find(x => { return x.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.1' })
|
||||||
|
if (nodeinfo) {
|
||||||
|
return await fetch(nodeinfo.href)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('HTTP Status was not 200')
|
||||||
|
}
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return {
|
||||||
|
software: {
|
||||||
|
name: res.software.name,
|
||||||
|
version: res.software.version,
|
||||||
|
homepage: 'https://activitypub.rocks'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(_ => {
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// NodeInfo version 1.0
|
||||||
|
{
|
||||||
|
const nodeinfo = nodeinfoRef.links.find(x => { return x.rel === 'http://nodeinfo.diaspora.software/ns/schema/1.0' })
|
||||||
|
if (nodeinfo) {
|
||||||
|
return await fetch(nodeinfo.href)
|
||||||
|
.then(res => {
|
||||||
|
if (res.status !== 200) {
|
||||||
|
throw new Error('HTTP Status was not 200')
|
||||||
|
}
|
||||||
|
return res.json()
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
return {
|
||||||
|
software: {
|
||||||
|
name: res.software.name,
|
||||||
|
version: res.software.version,
|
||||||
|
homepage: 'https://activitypub.rocks'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(_ => {
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/@/alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/@alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/@alice/123456',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/u/alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/users/alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/users/alice/123456',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'http://domain.org/alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
95
src/serviceProviders/aspe.js
Normal file
95
src/serviceProviders/aspe.js
Normal 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
|
||||||
|
}
|
||||||
|
]
|
127
src/serviceProviders/discord.js
Normal file
127
src/serviceProviders/discord.js
Normal 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
|
||||||
|
}
|
||||||
|
]
|
88
src/serviceProviders/discourse.js
Normal file
88
src/serviceProviders/discourse.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Discourse service provider ({@link https://docs.keyoxide.org/service-providers/discourse/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/discourse
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.activitypub.processURI('https://domain.example/u/alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/(.*)\/u\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'discourse',
|
||||||
|
name: 'Discourse',
|
||||||
|
homepage: 'https://www.discourse.org'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: `${match[2]}@${match[1]}`,
|
||||||
|
uri,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString().toString(),
|
||||||
|
uriIsAmbiguous: true
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||||
|
data: {
|
||||||
|
url: `https://${match[1]}/u/${match[2]}.json`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['user', 'bio_raw']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/u/alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/u/alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
86
src/serviceProviders/dns.js
Normal file
86
src/serviceProviders/dns.js
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* DNS service provider ({@link https://docs.keyoxide.org/service-providers/dns/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/dns
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.dns.processURI('dns:domain.example?type=TXT');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^dns:([a-zA-Z0-9.\-_]*)(?:\?(.*))?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'dns',
|
||||||
|
name: 'DNS'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: match[1],
|
||||||
|
uri: `https://${match[1]}`,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri: null,
|
||||||
|
fetcher: E.Fetcher.DNS,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.SERVER,
|
||||||
|
data: {
|
||||||
|
domain: match[1]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['records', 'txt']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'dns:domain.org',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'dns:domain.org?type=TXT',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
88
src/serviceProviders/forem.js
Normal file
88
src/serviceProviders/forem.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Forem service provider ({@link https://docs.keyoxide.org/service-providers/forem/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/forem
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.forem.processURI('https://domain.example/alice/title');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'forem',
|
||||||
|
name: 'Forem',
|
||||||
|
homepage: 'https://www.forem.com'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: `${match[2]}@${match[1]}`,
|
||||||
|
uri: `https://${match[1]}/${match[2]}`,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString().toString(),
|
||||||
|
uriIsAmbiguous: true
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||||
|
data: {
|
||||||
|
url: `https://${match[1]}/api/articles/${match[2]}/${match[3]}`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['body_markdown']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice/post',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice/post/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
101
src/serviceProviders/forgejo.js
Normal file
101
src/serviceProviders/forgejo.js
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
/*
|
||||||
|
Copyright 2023 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Forgejo service provider ({@link https://docs.keyoxide.org/service-providers/forgejo/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/forgejo
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.forgejo.processURI('https://domain.example/alice/repo');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { fetcher } from '../index.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'forgejo',
|
||||||
|
name: 'Forgejo',
|
||||||
|
homepage: 'https://forgejo.org'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: `${match[2]}@${match[1]}`,
|
||||||
|
uri: `https://${match[1]}/${match[2]}`,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: true
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||||
|
data: {
|
||||||
|
url: `https://${match[1]}/api/v1/repos/${match[2]}/${match[3]}`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.EQUALS,
|
||||||
|
path: ['description']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const functions = {
|
||||||
|
validate: async (/** @type {ServiceProvider} */ claimData, proofData, opts) => {
|
||||||
|
const url = `https://${new URL(claimData.proof.request.uri).hostname}/api/forgejo/v1/version`
|
||||||
|
const forgejoData = await fetcher.http.fn({ url, format: E.ProofFormat.JSON }, opts)
|
||||||
|
return forgejoData && 'version' in forgejoData
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice/forgejo_proof',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice/forgejo_proof/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice/other_proof',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
92
src/serviceProviders/gitea.js
Normal file
92
src/serviceProviders/gitea.js
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Gitea service provider ({@link https://docs.keyoxide.org/service-providers/gitea/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/gitea
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.gitea.processURI('https://domain.example/alice/repo');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/(.*)\/(.*)\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'gitea',
|
||||||
|
name: 'Gitea',
|
||||||
|
homepage: 'https://about.gitea.com'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: `${match[2]}@${match[1]}`,
|
||||||
|
uri: `https://${match[1]}/${match[2]}`,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: true
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||||
|
data: {
|
||||||
|
url: `https://${match[1]}/api/v1/repos/${match[2]}/${match[3]}`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.EQUALS,
|
||||||
|
path: ['description']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice/gitea_proof',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice/gitea_proof/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice/other_proof',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
96
src/serviceProviders/github.js
Normal file
96
src/serviceProviders/github.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Github service provider ({@link https://docs.keyoxide.org/service-providers/github/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/github
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.github.processURI('https://gist.github.com/alice/title');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/gist\.github\.com\/(.*)\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'github',
|
||||||
|
name: 'GitHub',
|
||||||
|
homepage: 'https://github.com'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: match[1],
|
||||||
|
uri: `https://github.com/${match[1]}`,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||||
|
data: {
|
||||||
|
url: `https://api.github.com/gists/${match[2]}`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [
|
||||||
|
{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['files', 'proof.md', 'content']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['files', 'openpgp.md', 'content']
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://gist.github.com/Alice/123456789',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://gist.github.com/Alice/123456789/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/Alice/123456789',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
|
@ -13,48 +13,65 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
*/
|
*/
|
||||||
const E = require('../enums')
|
/**
|
||||||
|
* 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');
|
||||||
|
*/
|
||||||
|
|
||||||
const reURI = /^https:\/\/(.*)\/(.*)\/gitlab_proof\/?/
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
const processURI = (uri) => {
|
export const reURI = /^https:\/\/(.*)\/(.*)\/gitlab_proof\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
const match = uri.match(reURI)
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
return {
|
return new ServiceProvider({
|
||||||
serviceprovider: {
|
about: {
|
||||||
type: 'web',
|
id: 'gitlab',
|
||||||
name: 'gitlab'
|
name: 'GitLab',
|
||||||
},
|
homepage: 'https://about.gitlab.com'
|
||||||
match: {
|
|
||||||
regularExpression: reURI,
|
|
||||||
isAmbiguous: true
|
|
||||||
},
|
},
|
||||||
profile: {
|
profile: {
|
||||||
display: `${match[2]}@${match[1]}`,
|
display: `${match[2]}@${match[1]}`,
|
||||||
uri: `https://${match[1]}/${match[2]}`,
|
uri: `https://${match[1]}/${match[2]}`,
|
||||||
qr: null
|
qr: null
|
||||||
},
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: true
|
||||||
|
},
|
||||||
proof: {
|
proof: {
|
||||||
uri: uri,
|
|
||||||
request: {
|
request: {
|
||||||
fetcher: E.Fetcher.HTTP,
|
fetcher: E.Fetcher.HTTP,
|
||||||
access: E.ProofAccess.GENERIC,
|
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||||
format: E.ProofFormat.JSON,
|
|
||||||
data: {
|
data: {
|
||||||
url: `https://${match[1]}/api/v4/projects/${match[2]}%2Fgitlab_proof`,
|
url: `https://${match[1]}/api/v4/projects/${match[2]}%2Fgitlab_proof`,
|
||||||
format: E.ProofFormat.JSON
|
format: E.ProofFormat.JSON
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
},
|
response: {
|
||||||
claim: {
|
format: E.ProofFormat.JSON
|
||||||
format: E.ClaimFormat.URI,
|
},
|
||||||
relation: E.ClaimRelation.EQUALS,
|
target: [{
|
||||||
path: ['description']
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.EQUALS,
|
||||||
|
path: ['description']
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const tests = [
|
export const tests = [
|
||||||
{
|
{
|
||||||
uri: 'https://gitlab.domain.org/alice/gitlab_proof',
|
uri: 'https://gitlab.domain.org/alice/gitlab_proof',
|
||||||
shouldMatch: true
|
shouldMatch: true
|
||||||
|
@ -68,7 +85,3 @@ const tests = [
|
||||||
shouldMatch: false
|
shouldMatch: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
exports.reURI = reURI
|
|
||||||
exports.processURI = processURI
|
|
||||||
exports.tests = tests
|
|
88
src/serviceProviders/hackernews.js
Normal file
88
src/serviceProviders/hackernews.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Hackernews service provider ({@link https://docs.keyoxide.org/service-providers/hackernews/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/hackernews
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.hackernews.processURI('https://news.ycombinator.com/user?id=alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/news\.ycombinator\.com\/user\?id=(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'hackernews',
|
||||||
|
name: 'Hacker News',
|
||||||
|
homepage: 'https://news.ycombinator.com'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: match[1],
|
||||||
|
uri,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri: `https://hacker-news.firebaseio.com/v0/user/${match[1]}.json`,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||||
|
data: {
|
||||||
|
url: `https://hacker-news.firebaseio.com/v0/user/${match[1]}.json`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.HTML,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['about']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://news.ycombinator.com/user?id=Alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://news.ycombinator.com/user?id=Alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/user?id=Alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
75
src/serviceProviders/index.js
Normal file
75
src/serviceProviders/index.js
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
import * as 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'
|
||||||
|
import * as matrix from './matrix.js'
|
||||||
|
import * as telegram from './telegram.js'
|
||||||
|
import * as twitter from './twitter.js'
|
||||||
|
import * as reddit from './reddit.js'
|
||||||
|
import * as liberapay from './liberapay.js'
|
||||||
|
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 gitea from './gitea.js'
|
||||||
|
import * as gitlab from './gitlab.js'
|
||||||
|
import * as github from './github.js'
|
||||||
|
import * as activitypub from './activitypub.js'
|
||||||
|
import * as discourse from './discourse.js'
|
||||||
|
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,
|
||||||
|
matrix,
|
||||||
|
telegram,
|
||||||
|
twitter,
|
||||||
|
reddit,
|
||||||
|
liberapay,
|
||||||
|
lichess,
|
||||||
|
hackernews,
|
||||||
|
lobsters,
|
||||||
|
forem,
|
||||||
|
forgejo,
|
||||||
|
gitea,
|
||||||
|
gitlab,
|
||||||
|
github,
|
||||||
|
activitypub,
|
||||||
|
discourse,
|
||||||
|
owncast,
|
||||||
|
stackexchange,
|
||||||
|
keybase,
|
||||||
|
opencollective,
|
||||||
|
orcid,
|
||||||
|
pronounscc,
|
||||||
|
discord
|
||||||
|
}
|
||||||
|
|
||||||
|
export const list = Object.keys(_data)
|
||||||
|
export { _data as data }
|
91
src/serviceProviders/irc.js
Normal file
91
src/serviceProviders/irc.js
Normal file
|
@ -0,0 +1,91 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* IRC service provider ({@link https://docs.keyoxide.org/service-providers/irc/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/irc
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.irc.processURI('irc://domain.example/alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^irc:\/\/(.*)\/([a-zA-Z0-9\-[\]\\`_^{|}]*)/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'irc',
|
||||||
|
name: 'IRC'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: `${match[1]}/${match[2]}`,
|
||||||
|
uri,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri: null,
|
||||||
|
fetcher: E.Fetcher.IRC,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.SERVER,
|
||||||
|
data: {
|
||||||
|
domain: match[1],
|
||||||
|
nick: match[2]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: []
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'irc://chat.ircserver.org/Alice1',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'irc://chat.ircserver.org/alice?param=123',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'irc://chat.ircserver.org/alice_bob',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://chat.ircserver.org/alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
88
src/serviceProviders/keybase.js
Normal file
88
src/serviceProviders/keybase.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Keybase service provider ({@link https://docs.keyoxide.org/service-providers/keybase/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/keybase
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.keybase.processURI('https://keybase.io/alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/keybase.io\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'keybase',
|
||||||
|
name: 'keybase',
|
||||||
|
homepage: 'https://keybase.io'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: match[1],
|
||||||
|
uri,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri: `https://keybase.io/_/api/1.0/user/lookup.json?username=${match[1]}`,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||||
|
data: {
|
||||||
|
url: `https://keybase.io/_/api/1.0/user/lookup.json?username=${match[1]}`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.FINGERPRINT,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['them', 'public_keys', 'primary', 'key_fingerprint']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://keybase.io/Alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://keybase.io/Alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/Alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
88
src/serviceProviders/liberapay.js
Normal file
88
src/serviceProviders/liberapay.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Liberapay service provider ({@link https://docs.keyoxide.org/service-providers/liberapay/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/liberapay
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.liberapay.processURI('https://liberapay.com/alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/liberapay\.com\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'liberapay',
|
||||||
|
name: 'Liberapay',
|
||||||
|
homepage: 'https://liberapay.com'
|
||||||
|
},
|
||||||
|
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: `https://liberapay.com/${match[1]}/public.json`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['statements', 'content']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://liberapay.com/alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://liberapay.com/alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
88
src/serviceProviders/lichess.js
Normal file
88
src/serviceProviders/lichess.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Lichess service provider ({@link https://docs.keyoxide.org/service-providers/lichess/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/lichess
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.lichess.processURI('https://lichess.org/@/alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/lichess\.org\/@\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'lichess',
|
||||||
|
name: 'Lichess',
|
||||||
|
homepage: 'https://lichess.org'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: match[1],
|
||||||
|
uri,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri: `https://lichess.org/api/user/${match[1]}`,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NONE,
|
||||||
|
data: {
|
||||||
|
url: `https://lichess.org/api/user/${match[1]}`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.FINGERPRINT,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['profile', 'links']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://lichess.org/@/Alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://lichess.org/@/Alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/@/Alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
96
src/serviceProviders/lobsters.js
Normal file
96
src/serviceProviders/lobsters.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Lobste.rs service provider ({@link https://docs.keyoxide.org/service-providers/lobsters/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/lobsters
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.lobsters.processURI('https://lobste.rs/~alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/lobste\.rs\/(?:~|u\/)(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'lobsters',
|
||||||
|
name: 'Lobsters',
|
||||||
|
homepage: 'https://lobste.rs'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: match[1],
|
||||||
|
uri: `https://lobste.rs/~${match[1]}`,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri: `https://lobste.rs/~${match[1]}.json`,
|
||||||
|
fetcher: E.Fetcher.HTTP,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||||
|
data: {
|
||||||
|
url: `https://lobste.rs/~${match[1]}.json`,
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['about']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://lobste.rs/~Alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://lobste.rs/u/Alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://lobste.rs/u/Alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/~Alice',
|
||||||
|
shouldMatch: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/u/Alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
114
src/serviceProviders/matrix.js
Normal file
114
src/serviceProviders/matrix.js
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
/*
|
||||||
|
Copyright 2021 Yarmo Mackenbach
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Matrix service provider ({@link https://docs.keyoxide.org/service-providers/matrix/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/matrix
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.matrix.processURI('matrix:u/...');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^matrix:u\/(?:@)?([^@:]*:[^?]*)(\?.*)?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
if (!match[2]) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = new URLSearchParams(match[2])
|
||||||
|
|
||||||
|
if (!(params.has('org.keyoxide.e') && params.has('org.keyoxide.r'))) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const paramRoomId = `${params.get('org.keyoxide.r')[0] !== '!' ? '!' : ''}${params.get('org.keyoxide.r')}`
|
||||||
|
const paramEventId = `${params.get('org.keyoxide.e')[0] !== '$' ? '$' : ''}${params.get('org.keyoxide.e')}`
|
||||||
|
|
||||||
|
const profileUrl = `https://matrix.to/#/@${match[1]}`
|
||||||
|
const eventUrl = `https://matrix.to/#/${paramRoomId}/${paramEventId}`
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'matrix',
|
||||||
|
name: 'Matrix',
|
||||||
|
homepage: 'https://matrix.org'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: `@${match[1]}`,
|
||||||
|
uri: profileUrl,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri: eventUrl,
|
||||||
|
fetcher: E.Fetcher.MATRIX,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.GRANTED,
|
||||||
|
data: {
|
||||||
|
eventId: paramEventId,
|
||||||
|
roomId: paramRoomId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['content', 'body']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri:
|
||||||
|
'matrix:u/alice:matrix.domain.org?org.keyoxide.r=123:domain.org&org.keyoxide.e=123',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'matrix:u/alice:matrix.domain.org',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri:
|
||||||
|
'matrix:u/@alice:matrix.domain.org?org.keyoxide.r=!123:domain.org&org.keyoxide.e=$123',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'xmpp:alice@domain.org',
|
||||||
|
shouldMatch: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/@alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
88
src/serviceProviders/opencollective.js
Normal file
88
src/serviceProviders/opencollective.js
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
/*
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* OpenCollective service provider ({@link https://docs.keyoxide.org/service-providers/opencollective/|Keyoxide docs})
|
||||||
|
* @module serviceProviders/opencollective
|
||||||
|
* @example
|
||||||
|
* import { ServiceProviderDefinitions } from 'doipjs';
|
||||||
|
* const sp = ServiceProviderDefinitions.data.opencollective.processURI('https://opencollective.com/alice');
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as E from '../enums.js'
|
||||||
|
import { ServiceProvider } from '../serviceProvider.js'
|
||||||
|
|
||||||
|
export const reURI = /^https:\/\/opencollective\.com\/(.*)\/?/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @function
|
||||||
|
* @param {string} uri - Claim URI to process
|
||||||
|
* @returns {ServiceProvider} The service provider information based on the claim URI
|
||||||
|
*/
|
||||||
|
export function processURI (uri) {
|
||||||
|
const match = uri.match(reURI)
|
||||||
|
|
||||||
|
return new ServiceProvider({
|
||||||
|
about: {
|
||||||
|
id: 'opencollective',
|
||||||
|
name: 'Open Collective',
|
||||||
|
homepage: 'https://opencollective.com'
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
display: match[1],
|
||||||
|
uri,
|
||||||
|
qr: null
|
||||||
|
},
|
||||||
|
claim: {
|
||||||
|
uriRegularExpression: reURI.toString(),
|
||||||
|
uriIsAmbiguous: false
|
||||||
|
},
|
||||||
|
proof: {
|
||||||
|
request: {
|
||||||
|
uri,
|
||||||
|
fetcher: E.Fetcher.GRAPHQL,
|
||||||
|
accessRestriction: E.ProofAccessRestriction.NOCORS,
|
||||||
|
data: {
|
||||||
|
url: 'https://api.opencollective.com/graphql/v2',
|
||||||
|
query: `{ "query": "query { account(slug: \\"${match[1]}\\") { longDescription } }" }`
|
||||||
|
}
|
||||||
|
},
|
||||||
|
response: {
|
||||||
|
format: E.ProofFormat.JSON
|
||||||
|
},
|
||||||
|
target: [{
|
||||||
|
format: E.ClaimFormat.URI,
|
||||||
|
encoding: E.EntityEncodingFormat.PLAIN,
|
||||||
|
relation: E.ClaimRelation.CONTAINS,
|
||||||
|
path: ['data', 'account', 'longDescription']
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
export const tests = [
|
||||||
|
{
|
||||||
|
uri: 'https://opencollective.com/Alice',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://opencollective.com/Alice/',
|
||||||
|
shouldMatch: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
uri: 'https://domain.org/Alice',
|
||||||
|
shouldMatch: false
|
||||||
|
}
|
||||||
|
]
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue