vestauth 0.18.2 → 0.20.0
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 +13 -1
- package/package.json +1 -1
- package/src/server/index.js +58 -10
- package/src/server/serializers/rotateSerializer.js +18 -0
- package/src/server/serializers/whoamiSerializer.js +11 -0
- package/src/server/services/rotateService.js +71 -0
- package/src/server/services/whoamiService.js +15 -0
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
|
+
[Unreleased](https://github.com/vestauth/vestauth/compare/v0.20.0...main)
|
|
6
|
+
|
|
7
|
+
## [0.20.0](https://github.com/vestauth/vestauth/compare/v0.19.0...v0.20.0) (2026-02-24)
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
* Add server logs ([#37](https://github.com/vestauth/vestauth/pull/37))
|
|
12
|
+
|
|
13
|
+
## [0.19.0](https://github.com/vestauth/vestauth/compare/v0.18.2...v0.19.0) (2026-02-24)
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
* Add `POST /rotate` ([#36](https://github.com/vestauth/vestauth/pull/36))
|
|
6
18
|
|
|
7
19
|
## [0.18.2](https://github.com/vestauth/vestauth/compare/v0.18.1...v0.18.2) (2026-02-24)
|
|
8
20
|
|
package/package.json
CHANGED
package/src/server/index.js
CHANGED
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
const { logger } = require('./../shared/logger')
|
|
2
|
-
const
|
|
2
|
+
const { version } = require('./../lib/helpers/packageJson')
|
|
3
3
|
const resolvePortAndHostname = require('./../lib/helpers/resolvePortAndHostname')
|
|
4
4
|
const subdomainBaseHost = require('./../lib/helpers/subdomainBaseHost')
|
|
5
5
|
const { connectOrm } = require('./models/index')
|
|
6
6
|
const RegisterService = require('./services/registerService')
|
|
7
7
|
const RegisterSerializer = require('./serializers/registerSerializer')
|
|
8
|
+
const RotateService = require('./services/rotateService')
|
|
9
|
+
const RotateSerializer = require('./serializers/rotateSerializer')
|
|
10
|
+
const WhoamiService = require('./services/whoamiService')
|
|
11
|
+
const WhoamiSerializer = require('./serializers/whoamiSerializer')
|
|
8
12
|
|
|
9
13
|
const express = require('express')
|
|
10
14
|
|
|
@@ -18,6 +22,22 @@ let HOSTNAME = null
|
|
|
18
22
|
|
|
19
23
|
const app = express()
|
|
20
24
|
app.use(express.json())
|
|
25
|
+
app.use((req, res, next) => {
|
|
26
|
+
const startedAt = Date.now()
|
|
27
|
+
|
|
28
|
+
res.on('finish', () => {
|
|
29
|
+
const durationMs = Date.now() - startedAt
|
|
30
|
+
const host = req.get('host') || '-'
|
|
31
|
+
const contentLength = res.getHeader('content-length') || '-'
|
|
32
|
+
|
|
33
|
+
logger.info(
|
|
34
|
+
`at=info method=${req.method} path="${req.originalUrl}" status=${res.statusCode} ` +
|
|
35
|
+
`host="${host}" duration_ms=${durationMs} bytes=${contentLength}`
|
|
36
|
+
)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
next()
|
|
40
|
+
})
|
|
21
41
|
|
|
22
42
|
app.use((req, res, next) => {
|
|
23
43
|
const hostNoPort = (req.headers.host || '').split(':')[0].toLowerCase()
|
|
@@ -44,7 +64,11 @@ app.get('/', (req, res) => {
|
|
|
44
64
|
if (req.agentUid) {
|
|
45
65
|
res.json({ uid: req.agentUid })
|
|
46
66
|
} else {
|
|
47
|
-
res.json({
|
|
67
|
+
res.json({
|
|
68
|
+
service: 'vestauth',
|
|
69
|
+
status: 'ok',
|
|
70
|
+
version
|
|
71
|
+
})
|
|
48
72
|
}
|
|
49
73
|
})
|
|
50
74
|
|
|
@@ -52,17 +76,14 @@ app.post('/register', async (req, res) => {
|
|
|
52
76
|
try {
|
|
53
77
|
const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`
|
|
54
78
|
|
|
55
|
-
const {
|
|
56
|
-
agent,
|
|
57
|
-
publicJwk,
|
|
58
|
-
isNew
|
|
59
|
-
} = await new RegisterService({
|
|
79
|
+
const attrs = {
|
|
60
80
|
models: app.models,
|
|
61
81
|
httpMethod: req.method,
|
|
62
82
|
uri: url,
|
|
63
83
|
headers: req.headers,
|
|
64
84
|
publicJwk: req.body.public_jwk
|
|
65
|
-
}
|
|
85
|
+
}
|
|
86
|
+
const { agent, publicJwk, isNew } = await new RegisterService(attrs).run()
|
|
66
87
|
|
|
67
88
|
const json = new RegisterSerializer({ agent, publicJwk, isNew }).run()
|
|
68
89
|
res.json(json)
|
|
@@ -87,9 +108,36 @@ app.get('/.well-known/http-message-signatures-directory', async (req, res) => {
|
|
|
87
108
|
app.get('/whoami', async (req, res) => {
|
|
88
109
|
try {
|
|
89
110
|
const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`
|
|
90
|
-
const
|
|
111
|
+
const attrs = {
|
|
112
|
+
httpMethod: req.method,
|
|
113
|
+
uri: url,
|
|
114
|
+
headers: req.headers
|
|
115
|
+
}
|
|
116
|
+
const agent = await new WhoamiService(attrs).run()
|
|
91
117
|
|
|
92
|
-
|
|
118
|
+
const json = new WhoamiSerializer({ agent }).run()
|
|
119
|
+
res.json(json)
|
|
120
|
+
} catch (err) {
|
|
121
|
+
logger.error(err)
|
|
122
|
+
res.status(401).json({ error: { status: 401, code: 401, message: err.message } })
|
|
123
|
+
}
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
app.post('/rotate', async (req, res) => {
|
|
127
|
+
try {
|
|
128
|
+
const url = `${req.protocol}://${req.get('host')}${req.originalUrl}`
|
|
129
|
+
|
|
130
|
+
const attrs = {
|
|
131
|
+
models: app.models,
|
|
132
|
+
httpMethod: req.method,
|
|
133
|
+
uri: url,
|
|
134
|
+
headers: req.headers,
|
|
135
|
+
publicJwk: req.body.public_jwk
|
|
136
|
+
}
|
|
137
|
+
const { agent, publicJwk } = await new RotateService(attrs).run()
|
|
138
|
+
|
|
139
|
+
const json = new RotateSerializer({ agent, publicJwk }).run()
|
|
140
|
+
res.json(json)
|
|
93
141
|
} catch (err) {
|
|
94
142
|
logger.error(err)
|
|
95
143
|
res.status(401).json({ error: { status: 401, code: 401, message: err.message } })
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
class RotateSerializer {
|
|
2
|
+
constructor ({ agent, publicJwk }) {
|
|
3
|
+
this.agent = agent
|
|
4
|
+
this.publicJwk = publicJwk
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
run () {
|
|
8
|
+
const agentFormatted = this.agent.toJSON()
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
uid: agentFormatted.uidFormatted,
|
|
12
|
+
kid: this.publicJwk.kid,
|
|
13
|
+
public_jwk: this.publicJwk.value
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
module.exports = RotateSerializer
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
const primitives = require('./../../lib/primitives')
|
|
2
|
+
const parseSignatureInputHeader = require('./../../lib/helpers/parseSignatureInputHeader')
|
|
3
|
+
|
|
4
|
+
class RotateService {
|
|
5
|
+
constructor ({ models, httpMethod, uri, headers, publicJwk }) {
|
|
6
|
+
this.models = models
|
|
7
|
+
this.httpMethod = httpMethod
|
|
8
|
+
this.uri = uri
|
|
9
|
+
this.headers = headers
|
|
10
|
+
this.publicJwk = publicJwk
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async run () {
|
|
14
|
+
const signatureInput = this.headers['Signature-Input'] || this.headers['signature-input']
|
|
15
|
+
const signatureInputValues = parseSignatureInputHeader(signatureInput)
|
|
16
|
+
|
|
17
|
+
const kid = signatureInputValues && signatureInputValues.keyid
|
|
18
|
+
if (!kid) throw new Error('kid missing')
|
|
19
|
+
|
|
20
|
+
const newKid = this.publicJwk && this.publicJwk.kid
|
|
21
|
+
if (!newKid) throw new Error('new kid missing')
|
|
22
|
+
|
|
23
|
+
const currentPublicJwk = await this.models.public_jwk.findOne({ kid })
|
|
24
|
+
if (!currentPublicJwk) throw new Error('public_jwk not found')
|
|
25
|
+
|
|
26
|
+
await primitives.verify(this.httpMethod, this.uri, this.headers, currentPublicJwk.value)
|
|
27
|
+
|
|
28
|
+
const agent = await this.models.agent.findOne({ id: currentPublicJwk.agent })
|
|
29
|
+
if (!agent) throw new Error('agent not found')
|
|
30
|
+
|
|
31
|
+
await this.models.public_jwk.db.transaction(async (trx) => {
|
|
32
|
+
const existingNewPublicJwk = await trx('public_jwks')
|
|
33
|
+
.select(['id', 'agent_id', 'kid'])
|
|
34
|
+
.where({ kid: newKid })
|
|
35
|
+
.first()
|
|
36
|
+
|
|
37
|
+
if (existingNewPublicJwk && Number(existingNewPublicJwk.agent_id) !== agent.id) {
|
|
38
|
+
throw new Error('new kid already belongs to another agent')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!existingNewPublicJwk) {
|
|
42
|
+
const now = new Date()
|
|
43
|
+
await trx('public_jwks').insert({
|
|
44
|
+
agent_id: agent.id,
|
|
45
|
+
kid: newKid,
|
|
46
|
+
value: this.publicJwk,
|
|
47
|
+
state: 'active',
|
|
48
|
+
created_at: now,
|
|
49
|
+
updated_at: now
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
await trx('public_jwks')
|
|
54
|
+
.where({ id: currentPublicJwk.id })
|
|
55
|
+
.update({
|
|
56
|
+
state: 'revoked',
|
|
57
|
+
updated_at: new Date()
|
|
58
|
+
})
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
const rotatedPublicJwk = await this.models.public_jwk.findOne({ kid: newKid })
|
|
62
|
+
if (!rotatedPublicJwk) throw new Error('rotated public_jwk not found')
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
agent,
|
|
66
|
+
publicJwk: rotatedPublicJwk
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = RotateService
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const tool = require('./../../lib/tool')
|
|
2
|
+
|
|
3
|
+
class WhoamiService {
|
|
4
|
+
constructor ({ httpMethod, uri, headers }) {
|
|
5
|
+
this.httpMethod = httpMethod
|
|
6
|
+
this.uri = uri
|
|
7
|
+
this.headers = headers
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
async run () {
|
|
11
|
+
return tool.verify(this.httpMethod, this.uri, this.headers)
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = WhoamiService
|