@travelclw/proof-protocol 0.1.2 → 0.2.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/README.md +81 -69
- package/browser.d.ts +42 -40
- package/browser.js +342 -306
- package/core.js +221 -219
- package/node.d.ts +80 -76
- package/package.json +1 -10
- package/server.js +316 -288
package/core.js
CHANGED
|
@@ -1,219 +1,221 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const REQUEST_PROOF_HEADER = 'x-ctx-proof'
|
|
4
|
-
const AUTH_CENTER_PROOF_AUDIENCE = 'auth-center'
|
|
5
|
-
const ODM_REQUEST_PROOF_AUDIENCE = 'uweb-odm'
|
|
6
|
-
const DEFAULT_REQUEST_PROOF_TIME_TOLERANCE_SECONDS = 2 * 60 * 60
|
|
7
|
-
|
|
8
|
-
const REQUEST_PROOF_ERROR_CODES = Object.freeze({
|
|
9
|
-
timeMismatch: 'E4601',
|
|
10
|
-
requestExpired: 'E4602',
|
|
11
|
-
requestUnverifiable: 'E4603',
|
|
12
|
-
sessionMismatch: 'E4604',
|
|
13
|
-
requestInvalid: 'E4605',
|
|
14
|
-
serviceUnavailable: 'E503',
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
class RequestProofValidationError extends Error {
|
|
18
|
-
constructor(code) {
|
|
19
|
-
super(code)
|
|
20
|
-
this.name = 'RequestProofValidationError'
|
|
21
|
-
this.code = code
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const compareText = (left, right) => (left < right ? -1 : left > right ? 1 : 0)
|
|
26
|
-
|
|
27
|
-
const normalizePath = value => {
|
|
28
|
-
const rawValue = String(value || '').trim()
|
|
29
|
-
if (!rawValue) return ''
|
|
30
|
-
|
|
31
|
-
let path = rawValue
|
|
32
|
-
if (/^[a-z][a-z\d+.-]*:\/\//i.test(rawValue)) {
|
|
33
|
-
try {
|
|
34
|
-
path = new URL(rawValue).pathname
|
|
35
|
-
} catch {
|
|
36
|
-
return ''
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const withoutQuery = path.split(/[?#]/)[0]
|
|
41
|
-
const withLeadingSlash = withoutQuery.startsWith('/') ? withoutQuery : `/${withoutQuery}`
|
|
42
|
-
return withLeadingSlash.replace(/\/{2,}/g, '/') || '/'
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
const canonicalizeQuery = value => {
|
|
46
|
-
const rawValue = String(value || '').trim()
|
|
47
|
-
if (!rawValue) return ''
|
|
48
|
-
|
|
49
|
-
let query = ''
|
|
50
|
-
if (/^[a-z][a-z\d+.-]*:\/\//i.test(rawValue)) {
|
|
51
|
-
try {
|
|
52
|
-
query = new URL(rawValue).search.slice(1)
|
|
53
|
-
} catch {
|
|
54
|
-
return ''
|
|
55
|
-
}
|
|
56
|
-
} else {
|
|
57
|
-
const withoutFragment = rawValue.split('#')[0]
|
|
58
|
-
const queryIndex = withoutFragment.indexOf('?')
|
|
59
|
-
query = queryIndex >= 0 ? withoutFragment.slice(queryIndex + 1) : ''
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
return Array.from(new URLSearchParams(query).entries())
|
|
63
|
-
.sort(([leftKey, leftValue], [rightKey, rightValue]) => {
|
|
64
|
-
const keyResult = compareText(leftKey, rightKey)
|
|
65
|
-
return keyResult || compareText(leftValue, rightValue)
|
|
66
|
-
})
|
|
67
|
-
.map(([key, entryValue]) => `${encodeURIComponent(key)}=${encodeURIComponent(entryValue)}`)
|
|
68
|
-
.join('&')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const normalizeJsonValue = value => {
|
|
72
|
-
if (value === null) return null
|
|
73
|
-
if (Array.isArray(value)) {
|
|
74
|
-
return value.map(item =>
|
|
75
|
-
item === undefined || typeof item === 'function' || typeof item === 'symbol'
|
|
76
|
-
? null
|
|
77
|
-
: normalizeJsonValue(item),
|
|
78
|
-
)
|
|
79
|
-
}
|
|
80
|
-
if (typeof value === 'number') return Number.isFinite(value) ? value : null
|
|
81
|
-
if (typeof value !== 'object') return value
|
|
82
|
-
if (typeof value.toJSON === 'function') return normalizeJsonValue(value.toJSON())
|
|
83
|
-
|
|
84
|
-
return Object.keys(value)
|
|
85
|
-
.sort(compareText)
|
|
86
|
-
.reduce((result, key) => {
|
|
87
|
-
const item = value[key]
|
|
88
|
-
if (item !== undefined && typeof item !== 'function' && typeof item !== 'symbol') {
|
|
89
|
-
result[key] = normalizeJsonValue(item)
|
|
90
|
-
}
|
|
91
|
-
return result
|
|
92
|
-
}, {})
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
const stableJson = value => JSON.stringify(normalizeJsonValue(value)) || ''
|
|
96
|
-
|
|
97
|
-
const formEntriesToObject = entries => {
|
|
98
|
-
const result = {}
|
|
99
|
-
for (const [key, value] of entries) {
|
|
100
|
-
const current = result[key]
|
|
101
|
-
result[key] =
|
|
102
|
-
current === undefined
|
|
103
|
-
? value
|
|
104
|
-
: Array.isArray(current)
|
|
105
|
-
? [...current, value]
|
|
106
|
-
: [current, value]
|
|
107
|
-
}
|
|
108
|
-
return result
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
const isBinaryBody = body => {
|
|
112
|
-
if (typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(body)) {
|
|
113
|
-
return true
|
|
114
|
-
}
|
|
115
|
-
if (typeof Blob !== 'undefined' && body instanceof Blob) return true
|
|
116
|
-
if (typeof ArrayBuffer !== 'undefined') {
|
|
117
|
-
return body instanceof ArrayBuffer || ArrayBuffer.isView(body)
|
|
118
|
-
}
|
|
119
|
-
return false
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const canonicalizeBody = (body, contentType, hasBody) => {
|
|
123
|
-
const normalizedContentType = String(contentType || '').toLowerCase()
|
|
124
|
-
if (hasBody === false) return 'none'
|
|
125
|
-
if (normalizedContentType.includes('multipart/form-data')) return 'multipart'
|
|
126
|
-
if (typeof FormData !== 'undefined' && body instanceof FormData) return 'multipart'
|
|
127
|
-
if (body === undefined || body === null || body === '') return 'none'
|
|
128
|
-
if (isBinaryBody(body)) return 'binary'
|
|
129
|
-
if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
|
|
130
|
-
return `form:${stableJson(formEntriesToObject(body.entries()))}`
|
|
131
|
-
}
|
|
132
|
-
if (typeof body === 'string') {
|
|
133
|
-
if (normalizedContentType.includes('application/x-www-form-urlencoded')) {
|
|
134
|
-
return `form:${stableJson(formEntriesToObject(new URLSearchParams(body).entries()))}`
|
|
135
|
-
}
|
|
136
|
-
if (normalizedContentType.includes('json')) {
|
|
137
|
-
try {
|
|
138
|
-
return `json:${stableJson(JSON.parse(body))}`
|
|
139
|
-
} catch {
|
|
140
|
-
return `text:${body}`
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
return `text:${body}`
|
|
144
|
-
}
|
|
145
|
-
if (normalizedContentType.includes('application/x-www-form-urlencoded')) {
|
|
146
|
-
return `form:${stableJson(body)}`
|
|
147
|
-
}
|
|
148
|
-
return `json:${stableJson(body)}`
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const isRequestProofRequired = value => String(value ?? '').trim() !== '0'
|
|
152
|
-
|
|
153
|
-
const normalizeProofMode = value => {
|
|
154
|
-
const mode = String(value ?? '').trim().toLowerCase()
|
|
155
|
-
if (mode === '0' || mode === 'off') return 'off'
|
|
156
|
-
if (mode === '1' || mode === 'shadow') return 'shadow'
|
|
157
|
-
return 'enforce'
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const isRequestProofSetupError = error => {
|
|
161
|
-
const message = error instanceof Error ? error.message : String(error || '')
|
|
162
|
-
return /^request_proof_(?:unsupported|storage_unsupported|storage_failed|read_failed|write_failed|reset_failed|sign_failed)$/.test(
|
|
163
|
-
message,
|
|
164
|
-
)
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const getRequestProofErrorCode = reason => {
|
|
168
|
-
if (reason === 'proof_expired') return REQUEST_PROOF_ERROR_CODES.timeMismatch
|
|
169
|
-
if (reason === 'proof_replayed') return REQUEST_PROOF_ERROR_CODES.requestExpired
|
|
170
|
-
if (
|
|
171
|
-
[
|
|
172
|
-
'missing_proof',
|
|
173
|
-
'missing_proof_binding',
|
|
174
|
-
'missing_bound_proof',
|
|
175
|
-
'missing_sso_source_proof',
|
|
176
|
-
'invalid_proof_key',
|
|
177
|
-
].includes(reason)
|
|
178
|
-
) {
|
|
179
|
-
return REQUEST_PROOF_ERROR_CODES.requestUnverifiable
|
|
180
|
-
}
|
|
181
|
-
if (
|
|
182
|
-
[
|
|
183
|
-
'missing_user',
|
|
184
|
-
'missing_token',
|
|
185
|
-
'missing_device_cookie',
|
|
186
|
-
'missing_binding',
|
|
187
|
-
'missing_session_context',
|
|
188
|
-
'
|
|
189
|
-
'
|
|
190
|
-
'
|
|
191
|
-
'
|
|
192
|
-
'
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const REQUEST_PROOF_HEADER = 'x-ctx-proof'
|
|
4
|
+
const AUTH_CENTER_PROOF_AUDIENCE = 'auth-center'
|
|
5
|
+
const ODM_REQUEST_PROOF_AUDIENCE = 'uweb-odm'
|
|
6
|
+
const DEFAULT_REQUEST_PROOF_TIME_TOLERANCE_SECONDS = 2 * 60 * 60
|
|
7
|
+
|
|
8
|
+
const REQUEST_PROOF_ERROR_CODES = Object.freeze({
|
|
9
|
+
timeMismatch: 'E4601',
|
|
10
|
+
requestExpired: 'E4602',
|
|
11
|
+
requestUnverifiable: 'E4603',
|
|
12
|
+
sessionMismatch: 'E4604',
|
|
13
|
+
requestInvalid: 'E4605',
|
|
14
|
+
serviceUnavailable: 'E503',
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
class RequestProofValidationError extends Error {
|
|
18
|
+
constructor(code) {
|
|
19
|
+
super(code)
|
|
20
|
+
this.name = 'RequestProofValidationError'
|
|
21
|
+
this.code = code
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const compareText = (left, right) => (left < right ? -1 : left > right ? 1 : 0)
|
|
26
|
+
|
|
27
|
+
const normalizePath = value => {
|
|
28
|
+
const rawValue = String(value || '').trim()
|
|
29
|
+
if (!rawValue) return ''
|
|
30
|
+
|
|
31
|
+
let path = rawValue
|
|
32
|
+
if (/^[a-z][a-z\d+.-]*:\/\//i.test(rawValue)) {
|
|
33
|
+
try {
|
|
34
|
+
path = new URL(rawValue).pathname
|
|
35
|
+
} catch {
|
|
36
|
+
return ''
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const withoutQuery = path.split(/[?#]/)[0]
|
|
41
|
+
const withLeadingSlash = withoutQuery.startsWith('/') ? withoutQuery : `/${withoutQuery}`
|
|
42
|
+
return withLeadingSlash.replace(/\/{2,}/g, '/') || '/'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const canonicalizeQuery = value => {
|
|
46
|
+
const rawValue = String(value || '').trim()
|
|
47
|
+
if (!rawValue) return ''
|
|
48
|
+
|
|
49
|
+
let query = ''
|
|
50
|
+
if (/^[a-z][a-z\d+.-]*:\/\//i.test(rawValue)) {
|
|
51
|
+
try {
|
|
52
|
+
query = new URL(rawValue).search.slice(1)
|
|
53
|
+
} catch {
|
|
54
|
+
return ''
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
const withoutFragment = rawValue.split('#')[0]
|
|
58
|
+
const queryIndex = withoutFragment.indexOf('?')
|
|
59
|
+
query = queryIndex >= 0 ? withoutFragment.slice(queryIndex + 1) : ''
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return Array.from(new URLSearchParams(query).entries())
|
|
63
|
+
.sort(([leftKey, leftValue], [rightKey, rightValue]) => {
|
|
64
|
+
const keyResult = compareText(leftKey, rightKey)
|
|
65
|
+
return keyResult || compareText(leftValue, rightValue)
|
|
66
|
+
})
|
|
67
|
+
.map(([key, entryValue]) => `${encodeURIComponent(key)}=${encodeURIComponent(entryValue)}`)
|
|
68
|
+
.join('&')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const normalizeJsonValue = value => {
|
|
72
|
+
if (value === null) return null
|
|
73
|
+
if (Array.isArray(value)) {
|
|
74
|
+
return value.map(item =>
|
|
75
|
+
item === undefined || typeof item === 'function' || typeof item === 'symbol'
|
|
76
|
+
? null
|
|
77
|
+
: normalizeJsonValue(item),
|
|
78
|
+
)
|
|
79
|
+
}
|
|
80
|
+
if (typeof value === 'number') return Number.isFinite(value) ? value : null
|
|
81
|
+
if (typeof value !== 'object') return value
|
|
82
|
+
if (typeof value.toJSON === 'function') return normalizeJsonValue(value.toJSON())
|
|
83
|
+
|
|
84
|
+
return Object.keys(value)
|
|
85
|
+
.sort(compareText)
|
|
86
|
+
.reduce((result, key) => {
|
|
87
|
+
const item = value[key]
|
|
88
|
+
if (item !== undefined && typeof item !== 'function' && typeof item !== 'symbol') {
|
|
89
|
+
result[key] = normalizeJsonValue(item)
|
|
90
|
+
}
|
|
91
|
+
return result
|
|
92
|
+
}, {})
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const stableJson = value => JSON.stringify(normalizeJsonValue(value)) || ''
|
|
96
|
+
|
|
97
|
+
const formEntriesToObject = entries => {
|
|
98
|
+
const result = {}
|
|
99
|
+
for (const [key, value] of entries) {
|
|
100
|
+
const current = result[key]
|
|
101
|
+
result[key] =
|
|
102
|
+
current === undefined
|
|
103
|
+
? value
|
|
104
|
+
: Array.isArray(current)
|
|
105
|
+
? [...current, value]
|
|
106
|
+
: [current, value]
|
|
107
|
+
}
|
|
108
|
+
return result
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const isBinaryBody = body => {
|
|
112
|
+
if (typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(body)) {
|
|
113
|
+
return true
|
|
114
|
+
}
|
|
115
|
+
if (typeof Blob !== 'undefined' && body instanceof Blob) return true
|
|
116
|
+
if (typeof ArrayBuffer !== 'undefined') {
|
|
117
|
+
return body instanceof ArrayBuffer || ArrayBuffer.isView(body)
|
|
118
|
+
}
|
|
119
|
+
return false
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const canonicalizeBody = (body, contentType, hasBody) => {
|
|
123
|
+
const normalizedContentType = String(contentType || '').toLowerCase()
|
|
124
|
+
if (hasBody === false) return 'none'
|
|
125
|
+
if (normalizedContentType.includes('multipart/form-data')) return 'multipart'
|
|
126
|
+
if (typeof FormData !== 'undefined' && body instanceof FormData) return 'multipart'
|
|
127
|
+
if (body === undefined || body === null || body === '') return 'none'
|
|
128
|
+
if (isBinaryBody(body)) return 'binary'
|
|
129
|
+
if (typeof URLSearchParams !== 'undefined' && body instanceof URLSearchParams) {
|
|
130
|
+
return `form:${stableJson(formEntriesToObject(body.entries()))}`
|
|
131
|
+
}
|
|
132
|
+
if (typeof body === 'string') {
|
|
133
|
+
if (normalizedContentType.includes('application/x-www-form-urlencoded')) {
|
|
134
|
+
return `form:${stableJson(formEntriesToObject(new URLSearchParams(body).entries()))}`
|
|
135
|
+
}
|
|
136
|
+
if (normalizedContentType.includes('json')) {
|
|
137
|
+
try {
|
|
138
|
+
return `json:${stableJson(JSON.parse(body))}`
|
|
139
|
+
} catch {
|
|
140
|
+
return `text:${body}`
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return `text:${body}`
|
|
144
|
+
}
|
|
145
|
+
if (normalizedContentType.includes('application/x-www-form-urlencoded')) {
|
|
146
|
+
return `form:${stableJson(body)}`
|
|
147
|
+
}
|
|
148
|
+
return `json:${stableJson(body)}`
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const isRequestProofRequired = value => String(value ?? '').trim() !== '0'
|
|
152
|
+
|
|
153
|
+
const normalizeProofMode = value => {
|
|
154
|
+
const mode = String(value ?? '').trim().toLowerCase()
|
|
155
|
+
if (mode === '0' || mode === 'off') return 'off'
|
|
156
|
+
if (mode === '1' || mode === 'shadow') return 'shadow'
|
|
157
|
+
return 'enforce'
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const isRequestProofSetupError = error => {
|
|
161
|
+
const message = error instanceof Error ? error.message : String(error || '')
|
|
162
|
+
return /^request_proof_(?:unsupported|storage_unsupported|storage_failed|read_failed|write_failed|reset_failed|sign_failed|key_missing|key_changed|session_missing|context_invalid)$/.test(
|
|
163
|
+
message,
|
|
164
|
+
)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const getRequestProofErrorCode = reason => {
|
|
168
|
+
if (reason === 'proof_expired') return REQUEST_PROOF_ERROR_CODES.timeMismatch
|
|
169
|
+
if (reason === 'proof_replayed') return REQUEST_PROOF_ERROR_CODES.requestExpired
|
|
170
|
+
if (
|
|
171
|
+
[
|
|
172
|
+
'missing_proof',
|
|
173
|
+
'missing_proof_binding',
|
|
174
|
+
'missing_bound_proof',
|
|
175
|
+
'missing_sso_source_proof',
|
|
176
|
+
'invalid_proof_key',
|
|
177
|
+
].includes(reason)
|
|
178
|
+
) {
|
|
179
|
+
return REQUEST_PROOF_ERROR_CODES.requestUnverifiable
|
|
180
|
+
}
|
|
181
|
+
if (
|
|
182
|
+
[
|
|
183
|
+
'missing_user',
|
|
184
|
+
'missing_token',
|
|
185
|
+
'missing_device_cookie',
|
|
186
|
+
'missing_binding',
|
|
187
|
+
'missing_session_context',
|
|
188
|
+
'proof_session_mismatch',
|
|
189
|
+
'proof_target_mismatch',
|
|
190
|
+
'token_mismatch',
|
|
191
|
+
'session_mismatch',
|
|
192
|
+
'session_id_mismatch',
|
|
193
|
+
'missing_sso_source_binding',
|
|
194
|
+
'sso_source_device_mismatch',
|
|
195
|
+
].includes(reason)
|
|
196
|
+
) {
|
|
197
|
+
return REQUEST_PROOF_ERROR_CODES.sessionMismatch
|
|
198
|
+
}
|
|
199
|
+
if (reason === 'missing_device_secret' || reason === 'verification_failed') {
|
|
200
|
+
return REQUEST_PROOF_ERROR_CODES.serviceUnavailable
|
|
201
|
+
}
|
|
202
|
+
return REQUEST_PROOF_ERROR_CODES.requestInvalid
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
module.exports = {
|
|
206
|
+
AUTH_CENTER_PROOF_AUDIENCE,
|
|
207
|
+
DEFAULT_REQUEST_PROOF_TIME_TOLERANCE_SECONDS,
|
|
208
|
+
ODM_REQUEST_PROOF_AUDIENCE,
|
|
209
|
+
REQUEST_PROOF_ERROR_CODES,
|
|
210
|
+
REQUEST_PROOF_HEADER,
|
|
211
|
+
RequestProofValidationError,
|
|
212
|
+
canonicalizeBody,
|
|
213
|
+
canonicalizeQuery,
|
|
214
|
+
getRequestProofErrorCode,
|
|
215
|
+
isRequestProofRequired,
|
|
216
|
+
isRequestProofSetupError,
|
|
217
|
+
normalizeJsonValue,
|
|
218
|
+
normalizePath,
|
|
219
|
+
normalizeProofMode,
|
|
220
|
+
stableJson,
|
|
221
|
+
}
|
package/node.d.ts
CHANGED
|
@@ -1,76 +1,80 @@
|
|
|
1
|
-
export type RequestProofPublicKey = {
|
|
2
|
-
kty: 'EC'
|
|
3
|
-
crv: 'P-256'
|
|
4
|
-
x: string
|
|
5
|
-
y: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export type NormalizedRequestProofKey = {
|
|
9
|
-
keyId: string
|
|
10
|
-
publicKey: RequestProofPublicKey
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export type RequestProofTimeWarning = {
|
|
14
|
-
reason: 'proof_expired'
|
|
15
|
-
issuedAt: number | null
|
|
16
|
-
serverNow: number
|
|
17
|
-
diffSeconds: number | null
|
|
18
|
-
toleranceSeconds: number
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
export function
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
1
|
+
export type RequestProofPublicKey = {
|
|
2
|
+
kty: 'EC'
|
|
3
|
+
crv: 'P-256'
|
|
4
|
+
x: string
|
|
5
|
+
y: string
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type NormalizedRequestProofKey = {
|
|
9
|
+
keyId: string
|
|
10
|
+
publicKey: RequestProofPublicKey
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export type RequestProofTimeWarning = {
|
|
14
|
+
reason: 'proof_expired'
|
|
15
|
+
issuedAt: number | null
|
|
16
|
+
serverNow: number
|
|
17
|
+
diffSeconds: number | null
|
|
18
|
+
toleranceSeconds: number
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function deriveProofSessionId(sessionId: string): string
|
|
22
|
+
|
|
23
|
+
export type VerifyRequestProofOptions = {
|
|
24
|
+
purpose?: 'session' | 'ticket'
|
|
25
|
+
sessionId?: string
|
|
26
|
+
proof: string
|
|
27
|
+
publicKey: RequestProofPublicKey
|
|
28
|
+
keyId: string
|
|
29
|
+
token: string
|
|
30
|
+
method: string
|
|
31
|
+
path: string
|
|
32
|
+
audience: string
|
|
33
|
+
timeToleranceSeconds?: number
|
|
34
|
+
body?: unknown
|
|
35
|
+
contentType?: unknown
|
|
36
|
+
hasBody?: boolean
|
|
37
|
+
requestId?: unknown
|
|
38
|
+
requireRequestBindings?: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type ProofServerMode = 'off' | 'shadow' | 'enforce'
|
|
42
|
+
|
|
43
|
+
export type ProofServerConfig = {
|
|
44
|
+
mode: ProofServerMode
|
|
45
|
+
deviceSessionSecret: string
|
|
46
|
+
deviceSessionTtlSeconds: number
|
|
47
|
+
timeToleranceSeconds: number
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export const REQUEST_PROOF_REASON_TEXT: Readonly<Record<string, string>>
|
|
51
|
+
export function getRequestProofReasonText(reason: string): string
|
|
52
|
+
|
|
53
|
+
export function hasTransferredRequestBody(options: {
|
|
54
|
+
body?: unknown
|
|
55
|
+
contentLength?: unknown
|
|
56
|
+
transferEncoding?: unknown
|
|
57
|
+
}): boolean
|
|
58
|
+
export function calculateRequestProofBindings(options: {
|
|
59
|
+
path: unknown
|
|
60
|
+
body?: unknown
|
|
61
|
+
contentType?: unknown
|
|
62
|
+
hasBody?: boolean
|
|
63
|
+
requestId?: unknown
|
|
64
|
+
}): { qsh: string; bth: string; rid: string }
|
|
65
|
+
export function normalizeRequestProofKey(value: unknown): Promise<NormalizedRequestProofKey | null>
|
|
66
|
+
export function getRequestProofKeyId(proofValue: string): string
|
|
67
|
+
export function verifyRequestProof(options: VerifyRequestProofOptions): Promise<{
|
|
68
|
+
jti: string
|
|
69
|
+
timeWarning: RequestProofTimeWarning | null
|
|
70
|
+
}>
|
|
71
|
+
export function normalizeProofMode(value: unknown): ProofServerMode
|
|
72
|
+
export function resolveProofServerConfig(options?: {
|
|
73
|
+
env?: Record<string, string | undefined>
|
|
74
|
+
mode?: string | number
|
|
75
|
+
deviceSessionSecret?: string
|
|
76
|
+
deviceSessionTtlSeconds?: number
|
|
77
|
+
timeToleranceSeconds?: number
|
|
78
|
+
}): ProofServerConfig
|
|
79
|
+
|
|
80
|
+
export { RequestProofValidationError } from './core'
|
package/package.json
CHANGED
|
@@ -1,18 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travelclw/proof-protocol",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Framework-independent browser and Node.js request Proof protocol",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "travelclw",
|
|
7
|
-
"repository": {
|
|
8
|
-
"type": "git",
|
|
9
|
-
"url": "git+https://gitee.com/bubbles_4/travelclw.git",
|
|
10
|
-
"directory": "packages/proof-protocol"
|
|
11
|
-
},
|
|
12
|
-
"homepage": "https://gitee.com/bubbles_4/travelclw/tree/main/packages/proof-protocol#readme",
|
|
13
|
-
"bugs": {
|
|
14
|
-
"url": "https://gitee.com/bubbles_4/travelclw/issues"
|
|
15
|
-
},
|
|
16
7
|
"keywords": [
|
|
17
8
|
"proof-of-possession",
|
|
18
9
|
"request-signing",
|