vestauth 0.6.0 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,7 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
4
4
 
5
- [Unreleased](https://github.com/vestauth/vestauth/compare/v0.5.3...main)
5
+ [Unreleased](https://github.com/vestauth/vestauth/compare/v0.7.1...main)
6
+
7
+ ## [0.7.1](https://github.com/vestauth/vestauth/compare/v0.7.0...v0.7.1) (2026-02-04)
8
+
9
+ ### Changed
10
+
11
+ * Raise an error if a bad `Signature-Agent` header ([#11](https://github.com/vestauth/vestauth/pull/11))
12
+
13
+ ## [0.7.0](https://github.com/vestauth/vestauth/compare/v0.6.0...v0.7.0) (2026-02-03)
14
+
15
+ ### Changed
16
+
17
+ * Change `key` terms to `jwk` for clarity ([#9](https://github.com/vestauth/vestauth/pull/9))
6
18
 
7
19
  ## [0.6.0](https://github.com/vestauth/vestauth/compare/v0.5.3...v0.6.0) (2026-02-03)
8
20
 
package/README.md CHANGED
@@ -4,6 +4,7 @@
4
4
 
5
5
  * identity
6
6
  * authentication
7
+ * verification
7
8
 
8
9
   
9
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vestauth",
3
- "version": "0.6.0",
3
+ "version": "0.7.1",
4
4
  "description": "auth for agents–from the creator of dotenvx",
5
5
  "keywords": [
6
6
  "vestauth"
@@ -9,7 +9,7 @@ async function headers (httpMethod, uri) {
9
9
  const options = this.opts()
10
10
  logger.debug(`options: ${JSON.stringify(options)}`)
11
11
 
12
- const output = await agent.headers(httpMethod, uri, options.id, options.privateKey, options.tag, options.nonce)
12
+ const output = await agent.headers(httpMethod, uri, options.id, options.privateJwk, options.tag, options.nonce)
13
13
 
14
14
  let space = 0
15
15
  if (options.prettyPrint) {
@@ -11,7 +11,7 @@ async function headers (httpMethod, uri) {
11
11
  const options = this.opts()
12
12
  logger.debug(`options: ${JSON.stringify(options)}`)
13
13
 
14
- const output = await primitives.headers(httpMethod, uri, options.id, options.privateKey, options.tag, options.nonce)
14
+ const output = await primitives.headers(httpMethod, uri, options.id, options.privateJwk, options.tag, options.nonce)
15
15
 
16
16
  let space = 0
17
17
  if (options.prettyPrint) {
@@ -6,11 +6,11 @@ function keypair () {
6
6
  const options = this.opts()
7
7
  logger.debug(`options: ${JSON.stringify(options)}`)
8
8
 
9
- const kp = primitives.keypair(options.privateKey, options.prefix)
9
+ const kp = primitives.keypair(options.privateJwk, options.prefix)
10
10
 
11
11
  const output = {
12
- public_key: kp.publicKey,
13
- private_key: kp.privateKey
12
+ public_jwk: kp.publicJwk,
13
+ private_jwk: kp.privateJwk
14
14
  }
15
15
 
16
16
  let space = 0
@@ -17,10 +17,10 @@ async function verify (httpMethod, uri) {
17
17
  'Signature-Agent': options.signatureAgent
18
18
  }
19
19
 
20
- const publicJwk = JSON.parse(options.publicKey || {})
20
+ const publicJwk = JSON.parse(options.publicJwk || {})
21
21
 
22
22
  const output = await primitives.verify(httpMethod, uri, headers, publicJwk)
23
- // const output = await primitive.verifyWebBotAuth(httpMethod, uri, signature, signatureInput, JSON.parse(publicKey))
23
+ // const output = await primitive.verifyWebBotAuth(httpMethod, uri, signature, signatureInput, JSON.parse(publicJwk))
24
24
 
25
25
  let space = 0
26
26
  if (options.prettyPrint) {
@@ -32,7 +32,7 @@ agent.command('headers')
32
32
  .argument('<httpMethod>', 'GET (default)')
33
33
  .argument('<uri>', '')
34
34
  .option('--id <id>', 'id (string)', env('AGENT_ID'))
35
- .option('--privateKey <privateKey>', 'AGENT_PUBLIC_KEY (default)', env('AGENT_PRIVATE_KEY'))
35
+ .option('--privateJwk <privateJwk>', 'AGENT_PRIVATE_JWK (default)', env('AGENT_PRIVATE_JWK'))
36
36
  .option('--tag <tag>', 'web-bot-auth (default) | web-bot-auth', 'web-bot-auth')
37
37
  .option('--nonce <nonce>', 'null (default)')
38
38
  .option('--pp, --pretty-print', 'pretty print output')
@@ -11,7 +11,7 @@ primitives
11
11
  const keypairAction = require('./../actions/primitives/keypair')
12
12
  primitives.command('keypair')
13
13
  .description('generate public/private keypair')
14
- .option('--private-key <privateKey>', 'pre-existing private key')
14
+ .option('--private-jwk <privateJwk>', 'pre-existing private JWK')
15
15
  .option('--prefix <type>', 'agent (default) | provider | none', 'agent')
16
16
  .option('--pp, --pretty-print', 'pretty print output')
17
17
  .action(keypairAction)
@@ -23,7 +23,7 @@ primitives.command('headers')
23
23
  .argument('<httpMethod>', 'GET (default)')
24
24
  .argument('<uri>', '')
25
25
  .option('--id <id>', 'id (string)', env('AGENT_ID'))
26
- .option('--private-key <privateKey>', 'private key (json string)', env('AGENT_PRIVATE_KEY'))
26
+ .option('--private-jwk <privateJwk>', 'private JWK (json string)', env('AGENT_PRIVATE_JWK'))
27
27
  .option('--tag <tag>', 'web-bot-auth (default) | web-bot-auth', 'web-bot-auth')
28
28
  .option('--nonce <nonce>', 'null (default)')
29
29
  .option('--pp, --pretty-print', 'pretty print output')
@@ -38,7 +38,7 @@ primitives.command('verify')
38
38
  .requiredOption('--signature <signature>', '')
39
39
  .requiredOption('--signature-input <signatureInput>', '')
40
40
  .option('--signature-agent <signatureAgent>', '')
41
- .option('--public-key <publicKey>', 'public key (json string)', env('AGENT_PUBLIC_KEY'))
41
+ .option('--public-jwk <publicJwk>', 'public JWK (json string)', env('AGENT_PUBLIC_JWK'))
42
42
  .option('--pp, --pretty-print', 'pretty print output')
43
43
  .action(verifyAction)
44
44
 
@@ -1,16 +1,16 @@
1
1
  const headers = require('./headers')
2
2
  const identity = require('./identity')
3
3
 
4
- async function agentHeaders (httpMethod, uri, id = null, privateKey = null, tag = 'web-bot-auth', nonce = null) {
5
- if (!privateKey) {
6
- privateKey = identity().privateKey
4
+ async function agentHeaders (httpMethod, uri, id = null, privateJwk = null, tag = 'web-bot-auth', nonce = null) {
5
+ if (!privateJwk) {
6
+ privateJwk = identity().privateJwk
7
7
  }
8
8
 
9
9
  if (!id) {
10
10
  id = identity().id
11
11
  }
12
12
 
13
- return await headers(httpMethod, uri, id, privateKey, tag, nonce)
13
+ return await headers(httpMethod, uri, id, privateJwk, tag, nonce)
14
14
  }
15
15
 
16
16
  module.exports = agentHeaders
@@ -8,21 +8,21 @@ async function agentInit () {
8
8
  const envPath = '.env'
9
9
 
10
10
  // keypair
11
- const currentPrivateKey = identity(false).privateKey
12
- const kp = keypair(currentPrivateKey, 'agent')
11
+ const currentPrivateJwk = identity(false).privateJwk
12
+ const kp = keypair(currentPrivateJwk, 'agent')
13
13
 
14
14
  touch(envPath)
15
15
 
16
16
  // must come before registration so that registration can send headers
17
- dotenvx.set('AGENT_PUBLIC_KEY', JSON.stringify(kp.publicKey), { path: envPath, plain: true, quiet: true })
18
- dotenvx.set('AGENT_PRIVATE_KEY', JSON.stringify(kp.privateKey), { path: envPath, plain: true, quiet: true })
17
+ dotenvx.set('AGENT_PUBLIC_JWK', JSON.stringify(kp.publicJwk), { path: envPath, plain: true, quiet: true })
18
+ dotenvx.set('AGENT_PRIVATE_JWK', JSON.stringify(kp.privateJwk), { path: envPath, plain: true, quiet: true })
19
19
 
20
20
  // register agent
21
- const agent = await new PostRegister(null, kp.publicKey).run()
21
+ const agent = await new PostRegister(null, kp.publicJwk).run()
22
22
  dotenvx.set('AGENT_ID', agent.uid, { path: envPath, plain: true, quiet: true })
23
23
 
24
24
  return {
25
- AGENT_PUBLIC_KEY: kp.publicKey,
25
+ AGENT_PUBLIC_JWK: kp.publicJwk,
26
26
  AGENT_ID: agent.uid,
27
27
  path: envPath,
28
28
  isNew: agent.is_new
@@ -2,6 +2,13 @@ function authorityMessage (uri, signatureParams) {
2
2
  const u = new URL(uri)
3
3
  const authority = u.host // includes port if present
4
4
 
5
+ // other possible message (target-uri, and signature-params)
6
+ // const message = [
7
+ // `"@method": ${method.toUpperCase()}`,
8
+ // `"@target-uri": ${uri}`,
9
+ // `"@signature-params": ${signatureParams}`
10
+ // ].join('\n')
11
+
5
12
  return [
6
13
  `"@authority": ${authority}`,
7
14
  `"@signature-params": ${signatureParams}`
@@ -14,9 +14,9 @@ class Errors {
14
14
  return e
15
15
  }
16
16
 
17
- missingPrivateKey () {
18
- const code = 'MISSING_PRIVATE_KEY'
19
- const message = `[${code}] missing --private-key (AGENT_PRIVATE_KEY)`
17
+ missingPrivateJwk () {
18
+ const code = 'MISSING_PRIVATE_JWK'
19
+ const message = `[${code}] missing --private-jwk (AGENT_PRIVATE_JWK)`
20
20
  const help = `[${code}] https://github.com/vestauth/vestauth/issues/5`
21
21
 
22
22
  const e = new Error(message)
@@ -25,9 +25,9 @@ class Errors {
25
25
  return e
26
26
  }
27
27
 
28
- invalidPrivateKey () {
29
- const code = 'INVALID_PRIVATE_KEY'
30
- const message = `[${code}] invalid --private-key (AGENT_PRIVATE_KEY)`
28
+ invalidPrivateJwk () {
29
+ const code = 'INVALID_PRIVATE_JWK'
30
+ const message = `[${code}] invalid --private-jwk (AGENT_PRIVATE_JWK)`
31
31
  const help = `[${code}] https://github.com/vestauth/vestauth/issues/7`
32
32
 
33
33
  const e = new Error(message)
@@ -36,6 +36,17 @@ class Errors {
36
36
  return e
37
37
  }
38
38
 
39
+ invalidSignatureAgent () {
40
+ const code = 'INVALID_SIGNATURE_AGENT'
41
+ const message = `[${code}] invalid --signature-agent`
42
+ const help = `[${code}] https://github.com/vestauth/vestauth/issues/11`
43
+
44
+ const e = new Error(message)
45
+ e.code = code
46
+ e.help = help
47
+ return e
48
+ }
49
+
39
50
  econnrefused () {
40
51
  const code = 'ECONNREFUSED'
41
52
  const message = `[${code}] connection refused`
@@ -3,18 +3,17 @@ const thumbprint = require('./thumbprint')
3
3
  const signatureParams = require('./signatureParams')
4
4
  const webBotAuthSignature = require('./webBotAuthSignature')
5
5
 
6
- async function headers (httpMethod, uri, id, privateKey, tag = 'web-bot-auth', nonce = null) {
6
+ async function headers (httpMethod, uri, id, privateJwk, tag = 'web-bot-auth', nonce = null) {
7
7
  if (!id) throw new Errors().missingId()
8
- if (!privateKey) throw new Errors().missingPrivateKey()
8
+ if (!privateJwk) throw new Errors().missingPrivateJwk()
9
9
 
10
- let privateJwk
11
10
  try {
12
- privateJwk = JSON.parse(privateKey)
11
+ privateJwk = JSON.parse(privateJwk)
13
12
  } catch {
14
- throw new Errors().invalidPrivateKey()
13
+ throw new Errors().invalidPrivateJwk()
15
14
  }
16
15
  if (!privateJwk || typeof privateJwk !== 'object') {
17
- throw new Errors().invalidPrivateKey()
16
+ throw new Errors().invalidPrivateJwk()
18
17
  }
19
18
 
20
19
  const kid = thumbprint(privateJwk)
@@ -25,7 +24,7 @@ async function headers (httpMethod, uri, id, privateKey, tag = 'web-bot-auth', n
25
24
  // const now = new Date()
26
25
  // return await signatureHeaders(
27
26
  // request,
28
- // await signerFromJWK(JSON.parse(privateKey)),
27
+ // await signerFromJWK(JSON.parse(privateJwk)),
29
28
  // {
30
29
  // created: now,
31
30
  // expires: new Date(now.getTime() + 300_000), // now + 5 min
@@ -34,7 +33,7 @@ async function headers (httpMethod, uri, id, privateKey, tag = 'web-bot-auth', n
34
33
 
35
34
  const signatureInput = signatureParams(privateJwk.kid, tag, nonce)
36
35
  const signature = webBotAuthSignature(httpMethod, uri, signatureInput, privateJwk)
37
- const signatureAgent = `https://${id}.agents.vestauth.com` // https://agent-1234.agents.vestauth.com/.well-known/http-message-signatures-directory
36
+ const signatureAgent = `${id}.agents.vestauth.com` // agent-1234.agents.vestauth.com (no scheme) /.well-known/http-message-signatures-directory
38
37
 
39
38
  return {
40
39
  Signature: `sig1=:${signature}:`,
@@ -1,16 +1,16 @@
1
1
  const env = require('./env')
2
2
 
3
3
  function identity (raiseError = true) {
4
- const publicKey = env('AGENT_PUBLIC_KEY')
5
- const privateKey = env('AGENT_PRIVATE_KEY')
4
+ const publicJwk = env('AGENT_PUBLIC_JWK')
5
+ const privateJwk = env('AGENT_PRIVATE_JWK')
6
6
  const id = env('AGENT_ID')
7
7
 
8
- if (raiseError && id && !(publicKey || !privateKey)) throw new Error('missing AGENT_PUBLIC_KEY, AGENT_PRIVATE_KEY, or AGENT_ID. Run [vestauth agent init]')
8
+ if (raiseError && id && !(publicJwk || !privateJwk)) throw new Error('missing AGENT_PUBLIC_JWK, AGENT_PRIVATE_JWK, or AGENT_ID. Run [vestauth agent init]')
9
9
 
10
10
  return {
11
11
  id,
12
- publicKey,
13
- privateKey
12
+ publicJwk,
13
+ privateJwk
14
14
  }
15
15
  }
16
16
 
@@ -2,11 +2,11 @@ const crypto = require('crypto')
2
2
 
3
3
  const thumbprint = require('./thumbprint')
4
4
 
5
- function keypair (existingPrivateKey, prefix = 'agent') {
5
+ function keypair (existingPrivateJwk, prefix = 'agent') {
6
6
  let publicJwk
7
7
  let privateJwk
8
8
 
9
- if (existingPrivateKey) {
9
+ if (existingPrivateJwk) {
10
10
  // example
11
11
  // {
12
12
  // "crv": "Ed25519",
@@ -15,9 +15,9 @@ function keypair (existingPrivateKey, prefix = 'agent') {
15
15
  // "kty": "OKP",
16
16
  // "kid": "rBE7_zLOVYk4oYEdI-01qpXHWNMyZYD-4LEf6HiyZ9Q"
17
17
  // }
18
- // (publicKey just remove 'd')
18
+ // (publicJwk just remove 'd')
19
19
 
20
- privateJwk = JSON.parse(existingPrivateKey)
20
+ privateJwk = JSON.parse(existingPrivateJwk)
21
21
  publicJwk = {
22
22
  crv: privateJwk.crv,
23
23
  x: privateJwk.x,
@@ -42,8 +42,8 @@ function keypair (existingPrivateKey, prefix = 'agent') {
42
42
  }
43
43
 
44
44
  return {
45
- publicKey: publicJwk,
46
- privateKey: privateJwk
45
+ publicJwk,
46
+ privateJwk
47
47
  }
48
48
  }
49
49
 
@@ -1,13 +1,14 @@
1
1
  const { parseDictionary } = require('structured-headers')
2
+ const Errors = require('./errors')
2
3
 
3
4
  // example: sig1=https://agent-9aa52a556ca85ee195866c0b.agents.vestauth.com
4
5
  function parseSignatureAgentHeader (signatureAgentHeader) {
5
6
  const dictionary = parseDictionary(signatureAgentHeader)
6
7
  const entry = dictionary.entries().next()
7
- if (entry.done) throw new Error('Invalid Signature-Agent header: empty dictionary')
8
+ if (entry.done) throw new Errors().invalidSignatureAgent()
8
9
  const [key, innerlist] = entry.value
9
10
  const value = innerlist[0].value
10
- if (!value) throw new Error('Invalid Signature-Agent header: expected a URL string')
11
+ if (!value) throw new Errors().invalidSignatureAgent()
11
12
 
12
13
  return {
13
14
  key,
@@ -0,0 +1,7 @@
1
+ const crypto = require('crypto')
2
+
3
+ function privateJwkObject (privateJwk) {
4
+ return crypto.createPrivateKey({ key: privateJwk, format: 'jwk' })
5
+ }
6
+
7
+ module.exports = privateJwkObject
@@ -1,13 +1,18 @@
1
1
  const { http } = require('./http')
2
2
  const buildApiError = require('./buildApiError')
3
3
  const parseSignatureAgentHeader = require('./parseSignatureAgentHeader')
4
+ const verifyAgentFqdn = require('./verifyAgentFqdn')
4
5
  const verify = require('./verify')
5
6
 
6
- async function providerVerify (httpMethod = 'GET', uri = 'https://api.vestauth.com/whoami', headers = {}) {
7
+ async function providerVerify (httpMethod, uri, headers = {}) {
7
8
  const signatureAgent = headers['Signature-Agent']
8
- const { value } = parseSignatureAgentHeader(signatureAgent) // sig1=https://agent-9aa52a556ca85ee195866c0b.agents.vestauth.com
9
+ const { value } = parseSignatureAgentHeader(signatureAgent) // sig1=agent-1234.agents.vestauth.com
10
+ const fqdn = value
11
+ verifyAgentFqdn(fqdn)
12
+ const origin = `https://${fqdn}` // https://agent-1234.agents.vestauth.com
9
13
 
10
- const url = `${value}/.well-known/http-message-signatures-directory`
14
+ // get jwks from .well-known
15
+ const url = `${origin}/.well-known/http-message-signatures-directory` // risk of SSRF ?
11
16
  const resp = await http(url, { method: 'GET', headers: { 'Content-Type': 'application/json' } })
12
17
  if (resp.statusCode >= 400) {
13
18
  const json = await resp.body.json()
@@ -26,6 +31,8 @@ async function providerVerify (httpMethod = 'GET', uri = 'https://api.vestauth.c
26
31
  // }
27
32
  // ]
28
33
  // }
34
+
35
+ // use publicJwk to verify
29
36
  const publicJwk = json.keys[0]
30
37
  const output = await verify(httpMethod, uri, headers, publicJwk)
31
38
 
@@ -1,7 +1,7 @@
1
1
  const crypto = require('crypto')
2
2
 
3
- function publicKeyObject (publicJwk) {
3
+ function publicJwkObject (publicJwk) {
4
4
  return crypto.createPublicKey({ key: publicJwk, format: 'jwk' })
5
5
  }
6
6
 
7
- module.exports = publicKeyObject
7
+ module.exports = publicJwkObject
@@ -3,7 +3,7 @@ const crypto = require('crypto')
3
3
  const parseSignatureInputHeader = require('./parseSignatureInputHeader')
4
4
  const stripDictionaryKey = require('./stripDictionaryKey')
5
5
  const authorityMessage = require('./authorityMessage')
6
- const publicKeyObject = require('./publicKeyObject')
6
+ const publicJwkObject = require('./publicJwkObject')
7
7
 
8
8
  function verify (httpMethod, uri, headers = {}, publicJwk) {
9
9
  const signature = headers.Signature
@@ -26,7 +26,7 @@ function verify (httpMethod, uri, headers = {}, publicJwk) {
26
26
  const success = crypto.verify(
27
27
  null,
28
28
  Buffer.from(message, 'utf8'),
29
- publicKeyObject(publicJwk),
29
+ publicJwkObject(publicJwk),
30
30
  Buffer.from(sig, 'base64')
31
31
  )
32
32
 
@@ -0,0 +1,28 @@
1
+ const DEFAULT_PROVIDER_FQDN_REGEX = /^[A-Za-z0-9-]+\.agents\.vestauth\.com$/
2
+ const Errors = require('./errors')
3
+
4
+ function getProviderFqdnRegex () {
5
+ const override = process.env.PROVIDER_FQDN_REGEX
6
+ if (!override) return DEFAULT_PROVIDER_FQDN_REGEX
7
+
8
+ try {
9
+ return new RegExp(override)
10
+ } catch {
11
+ return DEFAULT_PROVIDER_FQDN_REGEX
12
+ }
13
+ }
14
+
15
+ function verifyAgentFqdn (fqdn) {
16
+ if (!fqdn || typeof fqdn !== 'string') {
17
+ throw new Errors().invalidSignatureAgent()
18
+ }
19
+
20
+ const pattern = getProviderFqdnRegex()
21
+ if (!pattern.test(fqdn)) {
22
+ throw new Errors().invalidSignatureAgent()
23
+ }
24
+
25
+ return true
26
+ }
27
+
28
+ module.exports = verifyAgentFqdn
@@ -1,10 +1,10 @@
1
1
  const { verify } = require('web-bot-auth')
2
2
  const { verifierFromJWK } = require('web-bot-auth/crypto')
3
3
 
4
- async function verifyWebBotAuth (httpMethod, uri, signatureHeader, signatureInputHeader, publicKey) {
4
+ async function verifyWebBotAuth (httpMethod, uri, signatureHeader, signatureInputHeader, publicJwk) {
5
5
  let success = false
6
6
 
7
- const verifier = await verifierFromJWK(publicKey)
7
+ const verifier = await verifierFromJWK(publicJwk)
8
8
  const signedRequest = new Request(uri, {
9
9
  headers: {
10
10
  Signature: signatureHeader,
@@ -1,22 +1,14 @@
1
1
  const crypto = require('crypto')
2
- const edPrivateKeyObject = require('./edPrivateKeyObject')
2
+ const privateJwkObject = require('./privateJwkObject')
3
3
  const authorityMessage = require('./authorityMessage')
4
4
 
5
- function webBotAuthSignature (method = 'GET', uri = '', signatureParams, privateKey) {
5
+ function webBotAuthSignature (method = 'GET', uri = '', signatureParams, privateJwk) {
6
6
  const message = authorityMessage(uri, signatureParams)
7
7
 
8
- // const message = [
9
- // `"@method": ${method.toUpperCase()}`,
10
- // `"@target-uri": ${uri}`,
11
- // `"@signature-params": ${signatureParams}`
12
- // ].join('\n')
13
-
14
- const privateKeyObject = edPrivateKeyObject(privateKey)
15
-
16
8
  return crypto.sign(
17
9
  null,
18
10
  Buffer.from(message, 'utf8'),
19
- privateKeyObject
11
+ privateJwkObject(privateJwk)
20
12
  ).toString('base64')
21
13
  }
22
14
 
@@ -1,7 +0,0 @@
1
- const crypto = require('crypto')
2
-
3
- function edPrivateKeyObject (keyJson) {
4
- return crypto.createPrivateKey({ key: keyJson, format: 'jwk' })
5
- }
6
-
7
- module.exports = edPrivateKeyObject