update join command inspired by multiformats

This commit is contained in:
Andre Staltz 2024-01-10 18:50:15 +02:00
parent 8daee8c268
commit cbafdaa2fc
No known key found for this signature in database
GPG Key ID: 9EDE23EA7E8A4890
6 changed files with 85 additions and 31 deletions

View File

@ -1,5 +1,7 @@
// @ts-ignore
const MultiserverAddress = require('multiserver-address')
// @ts-ignore
const ip = require('ip')
const p = require('promisify-tuple')
/**
@ -10,7 +12,7 @@ const p = require('promisify-tuple')
* type: 'join',
* address: string,
* }} JoinCommand
* @typedef {`join/${string}/${string}/${string}/${string}`} JoinCommandStr
* @typedef {`join/${string}/${string}/${string}/${string}/${string}/${string}`} JoinCommandStr
* @typedef {{
* type: 'follow',
* id: string,
@ -52,32 +54,49 @@ const p = require('promisify-tuple')
* @returns {JoinCommand}
*/
function parseJoinCommand(pieces, uri) {
const [, host, port, pubkey, token] = pieces
if (!host) {
if (pieces.length < 7) {
// prettier-ignore
throw new Error(`Invalid URI "${uri}" for invite.parse, missing join hub address`)
throw new Error(`Invalid URI "${uri}" for invite.parse, missing some "join" arguments`)
}
// TODO: numeric validation for the port
if (!port) {
const [, hostFormat, host, transport, port, transform, cred] = pieces // pubkey, token] = pieces
if (hostFormat !== 'ip4' && hostFormat !== 'ip6' && hostFormat !== 'dns') {
// prettier-ignore
throw new Error(`Invalid URI "${uri}" for invite.parse, missing join hub port`)
throw new Error(`Invalid URI "${uri}" for invite.parse, unsupported "join" host format "${hostFormat}"`)
}
// TODO: base58 validation for the pubkey (and maybe length)
if (!pubkey) {
if (
(hostFormat === 'ip4' && !ip.isV4Format(host)) ||
(hostFormat === 'ip6' && !ip.isV6Format(host))
) {
// prettier-ignore
throw new Error(`Invalid URI "${uri}" for invite.parse, missing join hub pubkey`)
throw new Error(`Invalid URI "${uri}" for invite.parse, incoherent "join" host "${hostFormat}/${host}"`)
}
// TODO: base58 validation for the token, if present at all
if (!token) {
if (hostFormat === 'dns' && !host.includes('.')) {
// prettier-ignore
throw new Error(`Invalid URI "${uri}" for invite.parse, missing join hub token`)
throw new Error(`Invalid URI "${uri}" for invite.parse, invalid "join" host "${hostFormat}/${host}"`)
}
if (transport !== 'tcp') {
// prettier-ignore
throw new Error(`Invalid URI "${uri}" for invite.parse, unsupported "join" transport "${transport}"`)
}
const portNum = parseInt(port)
if (isNaN(portNum) || portNum < 0 || portNum > 65535) {
// prettier-ignore
throw new Error(`Invalid URI "${uri}" for invite.parse, invalid "join" port ${port}`)
}
if (transform !== 'shse') {
// prettier-ignore
throw new Error(`Invalid URI "${uri}" for invite.parse, unsupported "join" transform "${transform}"`)
}
// TODO: base58 validation for the shse pubkey (and maybe length)
// TODO: base58 validation for the shse token, if present at all
pieces.shift()
pieces.shift()
pieces.shift()
pieces.shift()
pieces.shift()
const shse = token === 'none' ? `shse:${pubkey}` : `shse:${pubkey}:${token}`
pieces.shift()
pieces.shift()
const shse = `shse:${cred.replace('.', ':')}`
const address = `net:${host}:${port}~${shse}` // TODO: add ws address here
return { type: 'join', address }
}
@ -273,6 +292,11 @@ function initInvite(peer, config) {
if (!net) return cb(new Error(ERROR_MSG))
if (net.name !== 'net') return cb(new Error(ERROR_MSG))
const [host, port] = net.data
const hostFormat = ip.isV4Format(host)
? 'ip4'
: ip.isV6Format('ipv6')
? 'ip6'
: 'dns'
if (!shse) return cb(new Error(ERROR_MSG))
if (shse.name !== 'shse') return cb(new Error(ERROR_MSG))
const [pubkey] = shse.data
@ -282,7 +306,7 @@ function initInvite(peer, config) {
if (err3) return cb(err3)
/** @type {JoinCommandStr} */
const joinCommand = `join/${host}/${port}/${pubkey}/${hubToken}`
const joinCommand = `join/${hostFormat}/${host}/tcp/${port}/shse/${pubkey}.${hubToken}`
/** @type {FollowCommandStr} */
const followCommand = `follow/${opts.id}`
/** @type {PromiseFollowCommandStr} */
@ -337,6 +361,11 @@ function initInvite(peer, config) {
if (!net) return cb(new Error(ERROR_MSG))
if (net.name !== 'net') return cb(new Error(ERROR_MSG))
const [host, port] = net.data
const hostFormat = ip.isV4Format(host)
? 'ip4'
: ip.isV6Format('ipv6')
? 'ip6'
: 'dns'
if (!shse) return cb(new Error(ERROR_MSG))
if (shse.name !== 'shse') return cb(new Error(ERROR_MSG))
const [pubkey] = shse.data
@ -347,7 +376,7 @@ function initInvite(peer, config) {
if (err3) return cb(err3)
/** @type {JoinCommandStr} */
const joinCommand = `join/${host}/${port}/${pubkey}/${hubToken}`
const joinCommand = `join/${hostFormat}/${host}/tcp/${port}/shse/${pubkey}.${hubToken}`
/** @type {TunnelConnectCommandStr} */
const tunnelCommand = `tunnel-connect/${pubkey}/${peer.shse.pubkey}`
/** @type {PromiseAccountAddCommandStr} */

View File

@ -23,6 +23,7 @@
"node": ">=16"
},
"dependencies": {
"ip": "~1.1.8",
"multiserver-address": "~1.0.1",
"promisify-tuple": "1.2.0"
},

View File

@ -5,13 +5,19 @@
**Invite URL:**
```
ppppp://invite/join/HOST/PORT/PUBKEY/TOKEN/follow/ALICE_ID/promise.follow/account.ALICE_ID/ALICE_TOKEN
ppppp://invite/join/HOSTFORMAT/HOST/TRANSPORT/PORT/TRANSFORM/CREDENTIALS/follow/ALICE_ID/promise.follow/account.ALICE_ID/ALICE_TOKEN
```
made of 3 "commands":
- `join/HOST/PORT/PUBKEY/TOKEN`
- `join/HOSTFORMAT/HOST/TRANSPORT/PORT/TRANSFORM/CREDENTIALS`
- Meaning "join" this hub at this address, claiming this token to become a member
- `HOSTFORMAT` is `ip4` or `ip6` or `dns`
- `HOST` is the host address
- `TRANSPORT` is `tcp` (or others to be supported in the future)
- `PORT` is the port number
- `TRANSFORM` is `shse` (or others to be supported in the future)
- `CREDENTIALS` is `PUBKEY.TOKEN` where PUBKEY is the hub's public key and TOKEN is the hub membership token to claim
- `follow/ALICE_ID`
- Meaning that you should follow Alice
- `promise.follow/account.ALICE_ID/ALICE_TOKEN`
@ -64,13 +70,19 @@ end
**Invite URL:**
```
ppppp://invite/join/HOST/PORT/PUBKEY/TOKEN/tunnel-connect/HUB_PUBKEY/OLD_PUBKEY/promise.account-add/peer.PUBKEY/OLD_TOKEN/promise.account-internal-encryption-key/peer.PUBKEY/OLD_TOKEN
ppppp://invite/join/HOSTFORMAT/HOST/TRANSPORT/PORT/TRANSFORM/CREDENTIALS/tunnel-connect/HUB_PUBKEY/OLD_PUBKEY/promise.account-add/peer.PUBKEY/OLD_TOKEN/promise.account-internal-encryption-key/peer.PUBKEY/OLD_TOKEN
```
made of 3 "commands":
- `join/HOST/PORT/PUBKEY/TOKEN`
- Meaning "join" this hub at this address, claiming this token
- `join/HOSTFORMAT/HOST/TRANSPORT/PORT/TRANSFORM/CREDENTIALS`
- Meaning "join" this hub at this address, claiming this token to become a member
- `HOSTFORMAT` is `ip4` or `ip6` or `dns`
- `HOST` is the host address
- `TRANSPORT` is `tcp` (or others to be supported in the future)
- `PORT` is the port number
- `TRANSFORM` is `shse` (or others to be supported in the future)
- `CREDENTIALS` is `PUBKEY.TOKEN` where PUBKEY is the hub's public key and TOKEN is the hub membership token to claim
- `tunnel-connect/HUB_PUBKEY/OLD_PUBKEY`
- Meaning that you should connect to the old device via a tunnel in the hub
- `promise.account-add/peer.PUBKEY/OLD_TOKEN` TODO implement with peer.PUBKEY

View File

@ -83,11 +83,11 @@ test('createForFriend()', async (t) => {
})
assert.equal(
uri,
`ppppp://invite/join/example.com/8008/HUB_PUBKEY/MOCK_TOKEN/follow/MOCK_ID/promise.follow/account.MOCK_ID/MOCK_PROMISE`
`ppppp://invite/join/dns/example.com/tcp/8008/shse/HUB_PUBKEY.MOCK_TOKEN/follow/MOCK_ID/promise.follow/account.MOCK_ID/MOCK_PROMISE`
)
assert.equal(
url,
`http://example.com/invite#ppppp%3A%2F%2Finvite%2Fjoin%2Fexample.com%2F8008%2FHUB_PUBKEY%2FMOCK_TOKEN%2Ffollow%2FMOCK_ID%2Fpromise.follow%2Faccount.MOCK_ID%2FMOCK_PROMISE`
`http://example.com/invite#ppppp%3A%2F%2Finvite%2Fjoin%2Fdns%2Fexample.com%2Ftcp%2F8008%2Fshse%2FHUB_PUBKEY.MOCK_TOKEN%2Ffollow%2FMOCK_ID%2Fpromise.follow%2Faccount.MOCK_ID%2FMOCK_PROMISE`
)
assert.ok(connectCalled)

View File

@ -81,11 +81,11 @@ test('createForMyself()', async (t) => {
})
assert.equal(
uri,
`ppppp://invite/join/example.com/8008/HUB_PUBKEY/MOCK_TOKEN/tunnel-connect/HUB_PUBKEY/${local.shse.pubkey}/promise.account-add/account.MOCK_ID/MOCK_PROMISE`
`ppppp://invite/join/dns/example.com/tcp/8008/shse/HUB_PUBKEY.MOCK_TOKEN/tunnel-connect/HUB_PUBKEY/${local.shse.pubkey}/promise.account-add/account.MOCK_ID/MOCK_PROMISE`
)
assert.equal(
url,
`http://example.com/invite#ppppp%3A%2F%2Finvite%2Fjoin%2Fexample.com%2F8008%2FHUB_PUBKEY%2FMOCK_TOKEN%2Ftunnel-connect%2FHUB_PUBKEY%2F${local.shse.pubkey}%2Fpromise.account-add%2Faccount.MOCK_ID%2FMOCK_PROMISE`
`http://example.com/invite#ppppp%3A%2F%2Finvite%2Fjoin%2Fdns%2Fexample.com%2Ftcp%2F8008%2Fshse%2FHUB_PUBKEY.MOCK_TOKEN%2Ftunnel-connect%2FHUB_PUBKEY%2F${local.shse.pubkey}%2Fpromise.account-add%2Faccount.MOCK_ID%2FMOCK_PROMISE`
)
assert.ok(connectCalled)

View File

@ -4,24 +4,24 @@ const plugin = require('../lib/index')
test('parse() error cases', (t) => {
assert.throws(() => {
plugin.parse('ssb://invite/join/HUB_ADDR/HUB_PUBKEY/HUB_TOKEN')
plugin.parse('ssb://invite/join/ip4/127.0.0.1/tcp/HUB_PUBKEY/HUB_TOKEN')
})
assert.throws(() => {
plugin.parse('ppppp:invite')
})
assert.throws(() => {
plugin.parse('ppppp:invite/join/HUB_ADDR')
plugin.parse('ppppp:invite/join/ip4/127.0.0.1')
})
})
test('parse() good friend invite', (t) => {
const commands = plugin.parse(
'ppppp://invite/join/HOST/PORT/PUBKEY/TOKEN/follow/ALICE/promise.follow/account.ALICE/ALICE_TOKEN'
'ppppp://invite/join/dns/example.com/tcp/8080/shse/PUBKEY.TOKEN/follow/ALICE/promise.follow/account.ALICE/ALICE_TOKEN'
)
assert.deepEqual(commands, [
{
type: 'join',
address: 'net:HOST:PORT~shse:PUBKEY:TOKEN',
address: 'net:example.com:8080~shse:PUBKEY:TOKEN',
},
{
type: 'follow',
@ -37,12 +37,12 @@ test('parse() good friend invite', (t) => {
test('parse() good myself invite', (t) => {
const commands = plugin.parse(
'ppppp://invite/join/HOST/PORT/PUBKEY/TOKEN/tunnel-connect/HUB_PUBKEY/OLD_PUBKEY/promise.account-add/account.ACCOUNT_ID/OLD_TOKEN'
'ppppp://invite/join/dns/example.com/tcp/8080/shse/PUBKEY.TOKEN/tunnel-connect/HUB_PUBKEY/OLD_PUBKEY/promise.account-add/account.ACCOUNT_ID/OLD_TOKEN'
)
assert.deepEqual(commands, [
{
type: 'join',
address: 'net:HOST:PORT~shse:PUBKEY:TOKEN',
address: 'net:example.com:8080~shse:PUBKEY:TOKEN',
},
{
type: 'tunnel-connect',
@ -55,3 +55,15 @@ test('parse() good myself invite', (t) => {
},
])
})
test('parse() good tokenless join invite', (t) => {
const commands = plugin.parse(
'ppppp://invite/join/dns/example.com/tcp/8080/shse/PUBKEY'
)
assert.deepEqual(commands, [
{
type: 'join',
address: 'net:example.com:8080~shse:PUBKEY',
},
])
})