-
Notifications
You must be signed in to change notification settings - Fork 3
/
middleware.js
119 lines (100 loc) · 3.77 KB
/
middleware.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
import {httpError, NOT_FOUND, ACCESS_DENIED, METHOD_NOT_ALLOWED, NOT_MODIFIED, OK, FOUND} from '../http/index.js'
import {getPolicy, getPipeline} from './engine.js'
import {getPackument} from './packument.js'
import {normalizePath, dropNullEntries, time, jsonBuffer} from '../util.js'
import {gzip} from '../zip.js'
import {hasHit, hasKey, isNoCache} from '../cache.js'
import {checkTarball} from './tarball.js'
import {logger} from '../logger.js'
import {getConfig} from '../config.js'
const warmupPipeline = (pipeline, opts, warmup = getConfig().warmup) => {
if (warmup <= 0 || isNoCache()) return
pipeline.forEach(([plugin, _opts]) => {
try {
plugin.warmup?.({...opts, ..._opts })
} catch (e) {
logger.error(`Error in plugin ${plugin.name} warmup`, e)
}
})
}
const warmupDepPackuments = (name, deps, boundContext, rules, warmup = getConfig().warmup) => {
if (warmup <= 0 || isNoCache()) return
const {registry, authorization, entrypoint, pipeline} = boundContext
logger.debug(`warmup ${name} deps`, deps)
deps.forEach(async (name) => {
if (hasHit(`packument-${name}`) || await hasKey(`packument-${name}`)) {
return
}
const org = name.charAt(0) === '@' ? name.slice(0, (name.indexOf('/') + 1 || name.indexOf('%') + 1) - 1) : null
try {
warmupPipeline(pipeline, {name, registry, org}, warmup--)
const {deps: _deps} = await getPackument({ boundContext: {registry, authorization, entrypoint, name, org, pipeline}, rules })
warmupDepPackuments(name, _deps, boundContext, rules, warmup--)
} catch (e) {
logger.warn('warmup error', e.message, e.stack)
}
})
}
const getAuth = (token, auth) => token
? token?.startsWith('Bearer')
? token
:`Bearer ${token}`
: auth
export const firewall = ({registry, rules, entrypoint: _entrypoint, token}) => async (req, res, next) => {
const {routeParams: {name, version, org}, base, method} = req
req.timed = true
if (method !== 'GET' && method !== 'HEAD') {
return next(httpError(METHOD_NOT_ALLOWED))
}
const config = getConfig()
const authorization = getAuth(token, req.headers['authorization'])
const entrypoint = _entrypoint || normalizePath(`${config.server.entrypoint}${base}`)
const pipeline = await getPipeline(rules)
const boundContext = { registry, entrypoint, authorization, name, org, version, pipeline }
warmupPipeline(pipeline, boundContext)
const [
{ packument, packumentBufferZip, headers, etag, deps, directives },
tarball
] = await Promise.all([
getPackument({ boundContext, rules }),
version ? checkTarball({registry, url: req.url}) : Promise.resolve(false)
])
if (!packument) {
return next(httpError(NOT_FOUND))
}
if (req.headers['if-none-match'] === etag) {
res.writeHead(NOT_MODIFIED).end()
return
}
warmupDepPackuments(name, deps, boundContext, rules)
// Tarball request
if (tarball) {
const policy = getPolicy(directives, version)
if (policy === 'warn') {
logger.warn(`${name}@${version}`, 'directive=', directives[version]._raw)
}
if (policy === 'deny') {
return next(httpError(ACCESS_DENIED))
}
return res
.writeHead(FOUND, {Location: tarball})
.end()
}
// Packument request
const isGzip = req.headers['accept-encoding']?.includes('gzip')
const buffer = isGzip
? packumentBufferZip || await time(gzip, `gzip packument ${name}`)(jsonBuffer(packument))
: jsonBuffer(packument)
const cl = '' + buffer.length
const extra = isGzip
? {'content-length': cl, 'transfer-encoding': null, 'content-encoding': 'gzip', etag}
: {'content-length': cl, 'transfer-encoding': null, 'content-encoding': null, etag}
res.writeHead(OK, dropNullEntries({
...headers,
...extra,
}))
if (method === 'GET') {
res.write(buffer)
}
res.end()
}