verdaccio-static-access-token 0.0.2

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.
@@ -0,0 +1,46 @@
1
+ version: 2.1
2
+
3
+ orbs:
4
+ node: circleci/node@7.2.1
5
+
6
+ jobs:
7
+ build:
8
+ docker:
9
+ - image: cimg/node:lts
10
+ steps:
11
+ - checkout
12
+ - node/install-packages:
13
+ pkg-manager: npm
14
+ - run:
15
+ name: Run tests
16
+ command: npm test
17
+ release:
18
+ docker:
19
+ - image: cimg/node:lts
20
+ steps:
21
+ - checkout
22
+ - node/install-packages:
23
+ pkg-manager: npm
24
+ - run:
25
+ name: Publish to npm
26
+ command: |
27
+ echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > ~/.npmrc
28
+ npm publish
29
+
30
+ workflows:
31
+ build-and-release:
32
+ jobs:
33
+ - build:
34
+ filters:
35
+ tags:
36
+ only: /.*/
37
+ - release:
38
+ requires:
39
+ - build
40
+ filters:
41
+ tags:
42
+ only: /^v[0-9]+(\.[0-9]+){2}$/
43
+ branches:
44
+ ignore: /.*/
45
+ context:
46
+ - VOODOO_NPM_RW
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Voodoo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,2 @@
1
+ # verdaccio-static-access-token
2
+ Static access token middleware plugin for Verdaccio 6+
@@ -0,0 +1,37 @@
1
+ storage: ./storage
2
+ plugins: ./plugins
3
+
4
+ web:
5
+ title: Verdaccio
6
+
7
+ middlewares:
8
+ static-access-token:
9
+ enabled: true
10
+ tokens:
11
+ - key: my-super-secret-key
12
+ user: verdaccio-user
13
+ readonly: true
14
+ - key: my-super-secret-key-with-write-perms
15
+ user: verdaccio-user
16
+ readonly: false
17
+
18
+ # You still need an auth plugin. For example, the default one:
19
+ auth:
20
+ htpasswd:
21
+ file: ./htpasswd
22
+
23
+ packages:
24
+ '@*/*':
25
+ access: $all
26
+ publish: verdaccio-user
27
+ unpublish: verdaccio-user
28
+ proxy: npmjs
29
+
30
+ '**':
31
+ access: $all
32
+ publish: verdaccio-user
33
+ unpublish: verdaccio-user
34
+ proxy: npmjs
35
+
36
+ logs:
37
+ - { type: stdout, format: pretty, level: http }
package/index.js ADDED
@@ -0,0 +1,115 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('crypto')
4
+
5
+ class StaticAccessTokenMiddleware {
6
+ constructor (config, stuff) {
7
+ this.stuff = stuff
8
+ this.stuff.logger.info('[verdaccio-static-access-token] Configuring')
9
+
10
+ this.enabled = config && config.enabled !== false
11
+
12
+ if (!this.enabled) {
13
+ this.stuff.logger.info('[verdaccio-static-access-token] Disabled')
14
+ this.tokens = []
15
+ return
16
+ }
17
+
18
+ this.tokens = config.tokens || []
19
+ }
20
+
21
+ // eslint-disable-next-line camelcase
22
+ register_middlewares (app, authInstance, storageInstance) {
23
+ if (!this.enabled) {
24
+ return
25
+ }
26
+ if (!this.tokens.length) {
27
+ this.stuff.logger.error('[verdaccio-static-access-token] No tokens configured, skipping middleware setup')
28
+ return
29
+ }
30
+
31
+ this.tokens.forEach(t => {
32
+ if (!t || !t.key || !t.user) {
33
+ throw new Error('[verdaccio-static-access-token] A token is missing a key or user.')
34
+ }
35
+ if (t.key.length < 16) {
36
+ throw new Error(`[verdaccio-static-access-token] Token "${t.key}" for user "${t.user}" is too insecure. Must be at least 16 characters long.`)
37
+ }
38
+ })
39
+
40
+ this.stuff.logger.info(`[verdaccio-static-access-token] register_middlewares loaded ${this.tokens.length} tokens`)
41
+
42
+ // Create a map of 'Bearer <token>' to token config for quick lookup
43
+ const accessTokens = new Map(this.tokens
44
+ .map(_ => `Bearer ${_.key}`)
45
+ .map((authHeader, i) => [authHeader, this.tokens[i]]))
46
+
47
+ // Verdaccio 6 might hide the secret in 'security.api.jwt.secret', fallback to 'secret'
48
+ const globalConfig = storageInstance.config
49
+ const verdaccioSecret = globalConfig.security.api.jwt.secret || globalConfig.secret
50
+
51
+ app.use((req, res, next) => {
52
+ // Just skip it LOL
53
+ if (!req.headers || !req.headers.authorization) {
54
+ return next()
55
+ }
56
+
57
+ if (req.headers && req.headers.authorization && accessTokens.has(req.headers.authorization)) {
58
+ const overwrite = accessTokens.get(req.headers.authorization)
59
+
60
+ // If the token is read-only we should forbid write methods
61
+ if (overwrite.readonly) {
62
+ const writeMethods = ['POST', 'PUT', 'DELETE', 'PATCH']
63
+ if (writeMethods.includes(req.method.toUpperCase())) {
64
+ this.stuff.logger.warn(`[verdaccio-static-access-token] Read-only token used for write method: ${req.method} ${req.url}`)
65
+ return res.status(403).send('Forbidden: Read-only token')
66
+ }
67
+ }
68
+
69
+ this.stuff.logger.info(`[verdaccio-static-access-token] Swapping static token for JWT User: ${overwrite.user}`)
70
+
71
+ // Generate a REAL JWT compatible with Verdaccio 6
72
+ req.headers.authorization = this._buildVerdaccio6JWT(
73
+ overwrite.user,
74
+ verdaccioSecret,
75
+ overwrite.readonly
76
+ )
77
+ }
78
+ next()
79
+ })
80
+ }
81
+
82
+ _buildVerdaccio6JWT (user, secret, readonly) {
83
+ const header = { alg: 'HS256', typ: 'JWT' }
84
+
85
+ // Payload must include standard Verdaccio groups!
86
+ const payload = {
87
+ name: user,
88
+ groups: readonly ? ['$all', '$authenticated', 'ci-readonly'] : ['$all', '$authenticated', '@all', '@authenticated', 'ci-readwrite'],
89
+ iat: Math.floor(Date.now() / 1000), // Issued at now
90
+ exp: Math.floor(Date.now() / 1000) + (60 * 60 * 24) // Expires in 1 day
91
+ }
92
+
93
+ const base64Url = (obj) => Buffer.from(JSON.stringify(obj))
94
+ .toString('base64')
95
+ .replace(/=/g, '')
96
+ .replace(/\+/g, '-')
97
+ .replace(/\//g, '_')
98
+
99
+ const encodedHeader = base64Url(header)
100
+ const encodedPayload = base64Url(payload)
101
+ const unsignedToken = `${encodedHeader}.${encodedPayload}`
102
+
103
+ const signature = crypto.createHmac('sha256', secret)
104
+ .update(unsignedToken)
105
+ .digest('base64')
106
+ .replace(/=/g, '')
107
+ .replace(/\+/g, '-')
108
+ .replace(/\//g, '_')
109
+
110
+ return `Bearer ${unsignedToken}.${signature}`
111
+ }
112
+ }
113
+ module.exports = (config, stuff) => {
114
+ return new StaticAccessTokenMiddleware(config, stuff)
115
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "verdaccio-static-access-token",
3
+ "version": "0.0.2",
4
+ "description": "Middleware for Verdaccio 6+ to support static access tokens for CI/CD workflows",
5
+ "main": "index.js",
6
+ "scripts": {
7
+ "test": "standard"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/VoodooTeam/verdaccio-static-access-token.git"
12
+ },
13
+ "keywords": [
14
+ "verdaccio",
15
+ "auth",
16
+ "plugin",
17
+ "verdaccio-plugin",
18
+ "token"
19
+ ],
20
+ "author": "Voodoo Gaming https://www.voodoo.io",
21
+ "license": "MIT",
22
+ "bugs": {
23
+ "url": "https://github.com/VoodooTeam/verdaccio-static-access-token/issues"
24
+ },
25
+ "homepage": "https://github.com/VoodooTeam/verdaccio-static-access-token#readme",
26
+ "dependencies": {},
27
+ "devDependencies": {
28
+ "standard": "^12.0.1",
29
+ "verdaccio": "^3.11.6"
30
+ }
31
+ }