@verdaccio/api 9.0.0-next-9.27 → 9.0.0-next-9.29
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/build/dist-tags.d.ts +4 -2
- package/build/dist-tags.js +4 -4
- package/build/dist-tags.js.map +1 -1
- package/build/dist-tags.mjs +4 -4
- package/build/dist-tags.mjs.map +1 -1
- package/build/index.js +30 -5
- package/build/index.js.map +1 -1
- package/build/index.mjs +30 -5
- package/build/index.mjs.map +1 -1
- package/build/publish.d.ts +4 -2
- package/build/publish.js +5 -5
- package/build/publish.js.map +1 -1
- package/build/publish.mjs +5 -5
- package/build/publish.mjs.map +1 -1
- package/build/require-otp.d.ts +55 -0
- package/build/require-otp.js +92 -0
- package/build/require-otp.js.map +1 -0
- package/build/require-otp.mjs +88 -0
- package/build/require-otp.mjs.map +1 -0
- package/build/stage.d.ts +17 -0
- package/build/stage.js +263 -0
- package/build/stage.js.map +1 -0
- package/build/stage.mjs +261 -0
- package/build/stage.mjs.map +1 -0
- package/build/user.d.ts +4 -2
- package/build/user.js +20 -14
- package/build/user.js.map +1 -1
- package/build/user.mjs +20 -14
- package/build/user.mjs.map +1 -1
- package/build/v1/login.js +1 -1
- package/build/v1/login.mjs +1 -1
- package/build/v1/profile.d.ts +12 -5
- package/build/v1/profile.js +65 -10
- package/build/v1/profile.js.map +1 -1
- package/build/v1/profile.mjs +65 -10
- package/build/v1/profile.mjs.map +1 -1
- package/build/v1/token.d.ts +4 -2
- package/build/v1/token.js +11 -4
- package/build/v1/token.js.map +1 -1
- package/build/v1/token.mjs +11 -4
- package/build/v1/token.mjs.map +1 -1
- package/package.json +11 -10
package/build/v1/profile.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
let _verdaccio_middleware = require("@verdaccio/middleware");
|
|
2
|
+
let _verdaccio_auth = require("@verdaccio/auth");
|
|
2
3
|
let _verdaccio_core = require("@verdaccio/core");
|
|
3
4
|
let lodash_es = require("lodash-es");
|
|
4
5
|
//#region src/v1/profile.ts
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
var TFA_MODES = ["auth-only", "auth-and-writes"];
|
|
7
|
+
function profile_default(route, auth, config, storage, logger, requireOtp = (_req, _res, next) => next()) {
|
|
8
|
+
const tfaStore = config.flags?.tfa && storage && logger ? new _verdaccio_auth.TfaStore(storage, config.secret, logger) : void 0;
|
|
9
|
+
function buildProfile(name, tfa = false) {
|
|
7
10
|
return {
|
|
8
|
-
tfa
|
|
11
|
+
tfa,
|
|
9
12
|
name,
|
|
10
13
|
email: "",
|
|
11
14
|
email_verified: false,
|
|
@@ -15,12 +18,62 @@ function profile_default(route, auth, config) {
|
|
|
15
18
|
fullname: ""
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
|
-
route.get(_verdaccio_middleware.PROFILE_API_ENDPOINTS.get_profile, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), function(req, res, next) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
route.get(_verdaccio_middleware.PROFILE_API_ENDPOINTS.get_profile, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), async function(req, res, next) {
|
|
22
|
+
const { name } = req.remote_user;
|
|
23
|
+
if ((0, lodash_es.isNil)(name)) {
|
|
24
|
+
res.status(_verdaccio_core.HTTP_STATUS.UNAUTHORIZED);
|
|
25
|
+
return next({ message: _verdaccio_core.API_ERROR.MUST_BE_LOGGED });
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return next(buildProfile(name, tfaStore ? await tfaStore.status(name) : false));
|
|
29
|
+
} catch (err) {
|
|
30
|
+
return next(err);
|
|
31
|
+
}
|
|
22
32
|
});
|
|
23
|
-
|
|
33
|
+
/** Promisified `auth.authenticate`, used to re-check the password. */
|
|
34
|
+
function verifyPassword(username, password) {
|
|
35
|
+
if (typeof password !== "string" || (0, lodash_es.isEmpty)(password)) return Promise.resolve(false);
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
auth.authenticate(username, password, (err) => resolve(!err));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The `tfa` body of `POST /-/npm/v1/user`, in the three shapes `npm profile`
|
|
42
|
+
* sends:
|
|
43
|
+
*
|
|
44
|
+
* { mode, password } start enrolment -> { tfa: 'otpauth://...' }
|
|
45
|
+
* [ '123456' ] finish enrolment -> { tfa: [recovery codes] }
|
|
46
|
+
* { mode: 'disable', password } turn it off -> the profile
|
|
47
|
+
*
|
|
48
|
+
* Step two must answer with a plain string: the CLI checks
|
|
49
|
+
* `/^otpauth:[/][/]/` and aborts otherwise. Step three must answer with an
|
|
50
|
+
* array, which the CLI prints as the recovery codes.
|
|
51
|
+
*/
|
|
52
|
+
async function handleTfa(tfa, name, next) {
|
|
53
|
+
const store = tfaStore;
|
|
54
|
+
if (Array.isArray(tfa)) {
|
|
55
|
+
const [code] = tfa;
|
|
56
|
+
const recoveryCodes = await store.completeEnrolment(name, String(code ?? ""));
|
|
57
|
+
if (!recoveryCodes) return next(_verdaccio_core.errorUtils.getUnauthorized("invalid one-time password"));
|
|
58
|
+
return next({ tfa: recoveryCodes });
|
|
59
|
+
}
|
|
60
|
+
if (typeof tfa !== "object" || tfa === null) return next(_verdaccio_core.errorUtils.getBadRequest("unsupported two-factor payload"));
|
|
61
|
+
const { mode, password } = tfa;
|
|
62
|
+
if (!await verifyPassword(name, password)) return next(_verdaccio_core.errorUtils.getUnauthorized(_verdaccio_core.API_ERROR.BAD_USERNAME_PASSWORD));
|
|
63
|
+
if (mode === "disable") {
|
|
64
|
+
await store.disable(name);
|
|
65
|
+
logger?.info({ name }, "two-factor authentication disabled for @{name}");
|
|
66
|
+
return next(buildProfile(name, false));
|
|
67
|
+
}
|
|
68
|
+
if (TFA_MODES.includes(mode) === false) return next(_verdaccio_core.errorUtils.getBadRequest(`two-factor mode must be one of ${TFA_MODES.join(", ")}`));
|
|
69
|
+
const { otpauthUrl } = await store.beginEnrolment(name, mode, config.web?.title ?? "Verdaccio");
|
|
70
|
+
logger?.info({
|
|
71
|
+
name,
|
|
72
|
+
mode
|
|
73
|
+
}, "two-factor enrolment started for @{name} in mode @{mode}");
|
|
74
|
+
return next({ tfa: otpauthUrl });
|
|
75
|
+
}
|
|
76
|
+
route.post(_verdaccio_middleware.PROFILE_API_ENDPOINTS.get_profile, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), requireOtp, function(req, res, next) {
|
|
24
77
|
if ((0, lodash_es.isNil)(req.remote_user.name)) {
|
|
25
78
|
res.status(_verdaccio_core.HTTP_STATUS.UNAUTHORIZED);
|
|
26
79
|
return next({ message: _verdaccio_core.API_ERROR.MUST_BE_LOGGED });
|
|
@@ -35,8 +88,10 @@ function profile_default(route, auth, config) {
|
|
|
35
88
|
if (isUpdated) return next(buildProfile(req.remote_user.name));
|
|
36
89
|
return next(_verdaccio_core.errorUtils.getInternalError(_verdaccio_core.API_ERROR.INTERNAL_SERVER_ERROR));
|
|
37
90
|
});
|
|
38
|
-
} else if ((0, lodash_es.isNil)(tfa) === false)
|
|
39
|
-
|
|
91
|
+
} else if ((0, lodash_es.isNil)(tfa) === false) {
|
|
92
|
+
if (!tfaStore) return next(_verdaccio_core.errorUtils.getCode(_verdaccio_core.HTTP_STATUS.SERVICE_UNAVAILABLE, _verdaccio_core.SUPPORT_ERRORS.TFA_DISABLED));
|
|
93
|
+
handleTfa(tfa, name, next).catch(next);
|
|
94
|
+
} else return next(_verdaccio_core.errorUtils.getCode(_verdaccio_core.HTTP_STATUS.INTERNAL_ERROR, _verdaccio_core.APP_ERROR.PROFILE_ERROR));
|
|
40
95
|
});
|
|
41
96
|
}
|
|
42
97
|
//#endregion
|
package/build/v1/profile.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile.js","names":[],"sources":["../../src/v1/profile.ts"],"sourcesContent":["import type { Response, Router } from 'express';\nimport { isEmpty, isNil } from 'lodash-es';\n\nimport type { Auth } from '@verdaccio/auth';\nimport {\n API_ERROR,\n APP_ERROR,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n errorUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { PROFILE_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Config } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nexport interface Profile {\n tfa:
|
|
1
|
+
{"version":3,"file":"profile.js","names":[],"sources":["../../src/v1/profile.ts"],"sourcesContent":["import type { RequestHandler, Response, Router } from 'express';\nimport { isEmpty, isNil } from 'lodash-es';\n\nimport type { Auth, TfaMode, TfaStatus } from '@verdaccio/auth';\nimport { TfaStore } from '@verdaccio/auth';\nimport {\n API_ERROR,\n APP_ERROR,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n errorUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { PROFILE_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nconst TFA_MODES: TfaMode[] = ['auth-only', 'auth-and-writes'];\n\nexport interface Profile {\n /**\n * `false` when two-factor is off, otherwise the mode and whether enrolment is\n * still half-finished. `npm profile` reads `tfa.mode` and `tfa.pending`.\n */\n tfa: TfaStatus;\n name: string;\n email: string;\n email_verified: boolean;\n created: string;\n updated: string;\n cidr_whitelist: string[] | null;\n fullname: string;\n}\n\nexport default function (\n route: Router,\n auth: Auth,\n config: Config,\n storage?: Storage,\n logger?: Logger,\n /** No-op unless the caller already has two-factor enabled. */\n requireOtp: RequestHandler = (_req, _res, next) => next()\n): void {\n // only built when the flag is on: without it nothing must touch the token store\n const tfaStore =\n config.flags?.tfa && storage && logger\n ? new TfaStore(storage, config.secret, logger)\n : undefined;\n\n function buildProfile(name: string, tfa: TfaStatus = false): Profile {\n return {\n tfa,\n name,\n email: '',\n email_verified: false,\n created: '',\n updated: '',\n cidr_whitelist: null,\n fullname: '',\n };\n }\n\n route.get(\n PROFILE_API_ENDPOINTS.get_profile,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n const { name } = req.remote_user;\n if (isNil(name)) {\n res.status(HTTP_STATUS.UNAUTHORIZED);\n return next({\n message: API_ERROR.MUST_BE_LOGGED,\n });\n }\n\n try {\n const tfa = tfaStore ? await tfaStore.status(name) : false;\n return next(buildProfile(name, tfa));\n } catch (err) {\n return next(err);\n }\n }\n );\n\n /** Promisified `auth.authenticate`, used to re-check the password. */\n function verifyPassword(username: string, password: unknown): Promise<boolean> {\n if (typeof password !== 'string' || isEmpty(password)) {\n return Promise.resolve(false);\n }\n return new Promise((resolve) => {\n auth.authenticate(username, password, (err) => resolve(!err));\n });\n }\n\n /**\n * The `tfa` body of `POST /-/npm/v1/user`, in the three shapes `npm profile`\n * sends:\n *\n * { mode, password } start enrolment -> { tfa: 'otpauth://...' }\n * [ '123456' ] finish enrolment -> { tfa: [recovery codes] }\n * { mode: 'disable', password } turn it off -> the profile\n *\n * Step two must answer with a plain string: the CLI checks\n * `/^otpauth:[/][/]/` and aborts otherwise. Step three must answer with an\n * array, which the CLI prints as the recovery codes.\n */\n async function handleTfa(tfa: any, name: string, next: $NextFunctionVer): Promise<void> {\n const store = tfaStore as TfaStore;\n\n // finish enrolment: `{ tfa: ['123456'] }`\n if (Array.isArray(tfa)) {\n const [code] = tfa;\n const recoveryCodes = await store.completeEnrolment(name, String(code ?? ''));\n if (!recoveryCodes) {\n return next(errorUtils.getUnauthorized('invalid one-time password'));\n }\n return next({ tfa: recoveryCodes });\n }\n\n if (typeof tfa !== 'object' || tfa === null) {\n return next(errorUtils.getBadRequest('unsupported two-factor payload'));\n }\n\n const { mode, password } = tfa;\n if (!(await verifyPassword(name, password))) {\n return next(errorUtils.getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));\n }\n\n if (mode === 'disable') {\n await store.disable(name);\n logger?.info({ name }, 'two-factor authentication disabled for @{name}');\n return next(buildProfile(name, false));\n }\n\n if (TFA_MODES.includes(mode) === false) {\n return next(\n errorUtils.getBadRequest(`two-factor mode must be one of ${TFA_MODES.join(', ')}`)\n );\n }\n\n const { otpauthUrl } = await store.beginEnrolment(name, mode, config.web?.title ?? 'Verdaccio');\n logger?.info({ name, mode }, 'two-factor enrolment started for @{name} in mode @{mode}');\n return next({ tfa: otpauthUrl });\n }\n\n route.post(\n PROFILE_API_ENDPOINTS.get_profile,\n rateLimit(config?.userRateLimit),\n // Turning two-factor off, switching its mode or changing the password are\n // exactly the operations a stolen session must not be able to perform on\n // its own. The middleware stays quiet while enrolment is pending, so the\n // three steps of `npm profile enable-2fa` are unaffected; `otplease` in the\n // CLI answers the challenge on the ones that follow.\n requireOtp,\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n if (isNil(req.remote_user.name)) {\n res.status(HTTP_STATUS.UNAUTHORIZED);\n return next({\n message: API_ERROR.MUST_BE_LOGGED,\n });\n }\n\n const { password, tfa } = req.body;\n const { name } = req.remote_user;\n\n if (isNil(password) === false) {\n if (\n validationUtils.validatePassword(\n password.new,\n config?.server?.passwordValidationRegex\n ) === false\n ) {\n /* eslint new-cap:off */\n return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT));\n }\n\n if (isEmpty(password.old)) {\n return next(errorUtils.getBadRequest('old password is required'));\n }\n\n auth.changePassword(\n name,\n password.old,\n password.new,\n (err, isUpdated): $NextFunctionVer => {\n if (err !== null) {\n return next(errorUtils.getForbidden(err.message));\n }\n\n if (isUpdated) {\n return next(buildProfile(req.remote_user.name));\n }\n return next(errorUtils.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));\n }\n );\n } else if (isNil(tfa) === false) {\n if (!tfaStore) {\n return next(\n errorUtils.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED)\n );\n }\n handleTfa(tfa, name, next).catch(next);\n } else {\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));\n }\n }\n );\n}\n"],"mappings":"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"}
|
package/build/v1/profile.mjs
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { PROFILE_API_ENDPOINTS, rateLimit } from "@verdaccio/middleware";
|
|
2
|
+
import { TfaStore } from "@verdaccio/auth";
|
|
2
3
|
import { API_ERROR, APP_ERROR, HTTP_STATUS, SUPPORT_ERRORS, errorUtils, validationUtils } from "@verdaccio/core";
|
|
3
4
|
import { isEmpty, isNil } from "lodash-es";
|
|
4
5
|
//#region src/v1/profile.ts
|
|
5
|
-
|
|
6
|
-
|
|
6
|
+
var TFA_MODES = ["auth-only", "auth-and-writes"];
|
|
7
|
+
function profile_default(route, auth, config, storage, logger, requireOtp = (_req, _res, next) => next()) {
|
|
8
|
+
const tfaStore = config.flags?.tfa && storage && logger ? new TfaStore(storage, config.secret, logger) : void 0;
|
|
9
|
+
function buildProfile(name, tfa = false) {
|
|
7
10
|
return {
|
|
8
|
-
tfa
|
|
11
|
+
tfa,
|
|
9
12
|
name,
|
|
10
13
|
email: "",
|
|
11
14
|
email_verified: false,
|
|
@@ -15,12 +18,62 @@ function profile_default(route, auth, config) {
|
|
|
15
18
|
fullname: ""
|
|
16
19
|
};
|
|
17
20
|
}
|
|
18
|
-
route.get(PROFILE_API_ENDPOINTS.get_profile, rateLimit(config?.userRateLimit), function(req, res, next) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
21
|
+
route.get(PROFILE_API_ENDPOINTS.get_profile, rateLimit(config?.userRateLimit), async function(req, res, next) {
|
|
22
|
+
const { name } = req.remote_user;
|
|
23
|
+
if (isNil(name)) {
|
|
24
|
+
res.status(HTTP_STATUS.UNAUTHORIZED);
|
|
25
|
+
return next({ message: API_ERROR.MUST_BE_LOGGED });
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
return next(buildProfile(name, tfaStore ? await tfaStore.status(name) : false));
|
|
29
|
+
} catch (err) {
|
|
30
|
+
return next(err);
|
|
31
|
+
}
|
|
22
32
|
});
|
|
23
|
-
|
|
33
|
+
/** Promisified `auth.authenticate`, used to re-check the password. */
|
|
34
|
+
function verifyPassword(username, password) {
|
|
35
|
+
if (typeof password !== "string" || isEmpty(password)) return Promise.resolve(false);
|
|
36
|
+
return new Promise((resolve) => {
|
|
37
|
+
auth.authenticate(username, password, (err) => resolve(!err));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The `tfa` body of `POST /-/npm/v1/user`, in the three shapes `npm profile`
|
|
42
|
+
* sends:
|
|
43
|
+
*
|
|
44
|
+
* { mode, password } start enrolment -> { tfa: 'otpauth://...' }
|
|
45
|
+
* [ '123456' ] finish enrolment -> { tfa: [recovery codes] }
|
|
46
|
+
* { mode: 'disable', password } turn it off -> the profile
|
|
47
|
+
*
|
|
48
|
+
* Step two must answer with a plain string: the CLI checks
|
|
49
|
+
* `/^otpauth:[/][/]/` and aborts otherwise. Step three must answer with an
|
|
50
|
+
* array, which the CLI prints as the recovery codes.
|
|
51
|
+
*/
|
|
52
|
+
async function handleTfa(tfa, name, next) {
|
|
53
|
+
const store = tfaStore;
|
|
54
|
+
if (Array.isArray(tfa)) {
|
|
55
|
+
const [code] = tfa;
|
|
56
|
+
const recoveryCodes = await store.completeEnrolment(name, String(code ?? ""));
|
|
57
|
+
if (!recoveryCodes) return next(errorUtils.getUnauthorized("invalid one-time password"));
|
|
58
|
+
return next({ tfa: recoveryCodes });
|
|
59
|
+
}
|
|
60
|
+
if (typeof tfa !== "object" || tfa === null) return next(errorUtils.getBadRequest("unsupported two-factor payload"));
|
|
61
|
+
const { mode, password } = tfa;
|
|
62
|
+
if (!await verifyPassword(name, password)) return next(errorUtils.getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));
|
|
63
|
+
if (mode === "disable") {
|
|
64
|
+
await store.disable(name);
|
|
65
|
+
logger?.info({ name }, "two-factor authentication disabled for @{name}");
|
|
66
|
+
return next(buildProfile(name, false));
|
|
67
|
+
}
|
|
68
|
+
if (TFA_MODES.includes(mode) === false) return next(errorUtils.getBadRequest(`two-factor mode must be one of ${TFA_MODES.join(", ")}`));
|
|
69
|
+
const { otpauthUrl } = await store.beginEnrolment(name, mode, config.web?.title ?? "Verdaccio");
|
|
70
|
+
logger?.info({
|
|
71
|
+
name,
|
|
72
|
+
mode
|
|
73
|
+
}, "two-factor enrolment started for @{name} in mode @{mode}");
|
|
74
|
+
return next({ tfa: otpauthUrl });
|
|
75
|
+
}
|
|
76
|
+
route.post(PROFILE_API_ENDPOINTS.get_profile, rateLimit(config?.userRateLimit), requireOtp, function(req, res, next) {
|
|
24
77
|
if (isNil(req.remote_user.name)) {
|
|
25
78
|
res.status(HTTP_STATUS.UNAUTHORIZED);
|
|
26
79
|
return next({ message: API_ERROR.MUST_BE_LOGGED });
|
|
@@ -35,8 +88,10 @@ function profile_default(route, auth, config) {
|
|
|
35
88
|
if (isUpdated) return next(buildProfile(req.remote_user.name));
|
|
36
89
|
return next(errorUtils.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));
|
|
37
90
|
});
|
|
38
|
-
} else if (isNil(tfa) === false)
|
|
39
|
-
|
|
91
|
+
} else if (isNil(tfa) === false) {
|
|
92
|
+
if (!tfaStore) return next(errorUtils.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED));
|
|
93
|
+
handleTfa(tfa, name, next).catch(next);
|
|
94
|
+
} else return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));
|
|
40
95
|
});
|
|
41
96
|
}
|
|
42
97
|
//#endregion
|
package/build/v1/profile.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"profile.mjs","names":[],"sources":["../../src/v1/profile.ts"],"sourcesContent":["import type { Response, Router } from 'express';\nimport { isEmpty, isNil } from 'lodash-es';\n\nimport type { Auth } from '@verdaccio/auth';\nimport {\n API_ERROR,\n APP_ERROR,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n errorUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { PROFILE_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Config } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nexport interface Profile {\n tfa:
|
|
1
|
+
{"version":3,"file":"profile.mjs","names":[],"sources":["../../src/v1/profile.ts"],"sourcesContent":["import type { RequestHandler, Response, Router } from 'express';\nimport { isEmpty, isNil } from 'lodash-es';\n\nimport type { Auth, TfaMode, TfaStatus } from '@verdaccio/auth';\nimport { TfaStore } from '@verdaccio/auth';\nimport {\n API_ERROR,\n APP_ERROR,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n errorUtils,\n validationUtils,\n} from '@verdaccio/core';\nimport { PROFILE_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\nconst TFA_MODES: TfaMode[] = ['auth-only', 'auth-and-writes'];\n\nexport interface Profile {\n /**\n * `false` when two-factor is off, otherwise the mode and whether enrolment is\n * still half-finished. `npm profile` reads `tfa.mode` and `tfa.pending`.\n */\n tfa: TfaStatus;\n name: string;\n email: string;\n email_verified: boolean;\n created: string;\n updated: string;\n cidr_whitelist: string[] | null;\n fullname: string;\n}\n\nexport default function (\n route: Router,\n auth: Auth,\n config: Config,\n storage?: Storage,\n logger?: Logger,\n /** No-op unless the caller already has two-factor enabled. */\n requireOtp: RequestHandler = (_req, _res, next) => next()\n): void {\n // only built when the flag is on: without it nothing must touch the token store\n const tfaStore =\n config.flags?.tfa && storage && logger\n ? new TfaStore(storage, config.secret, logger)\n : undefined;\n\n function buildProfile(name: string, tfa: TfaStatus = false): Profile {\n return {\n tfa,\n name,\n email: '',\n email_verified: false,\n created: '',\n updated: '',\n cidr_whitelist: null,\n fullname: '',\n };\n }\n\n route.get(\n PROFILE_API_ENDPOINTS.get_profile,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer): Promise<void> {\n const { name } = req.remote_user;\n if (isNil(name)) {\n res.status(HTTP_STATUS.UNAUTHORIZED);\n return next({\n message: API_ERROR.MUST_BE_LOGGED,\n });\n }\n\n try {\n const tfa = tfaStore ? await tfaStore.status(name) : false;\n return next(buildProfile(name, tfa));\n } catch (err) {\n return next(err);\n }\n }\n );\n\n /** Promisified `auth.authenticate`, used to re-check the password. */\n function verifyPassword(username: string, password: unknown): Promise<boolean> {\n if (typeof password !== 'string' || isEmpty(password)) {\n return Promise.resolve(false);\n }\n return new Promise((resolve) => {\n auth.authenticate(username, password, (err) => resolve(!err));\n });\n }\n\n /**\n * The `tfa` body of `POST /-/npm/v1/user`, in the three shapes `npm profile`\n * sends:\n *\n * { mode, password } start enrolment -> { tfa: 'otpauth://...' }\n * [ '123456' ] finish enrolment -> { tfa: [recovery codes] }\n * { mode: 'disable', password } turn it off -> the profile\n *\n * Step two must answer with a plain string: the CLI checks\n * `/^otpauth:[/][/]/` and aborts otherwise. Step three must answer with an\n * array, which the CLI prints as the recovery codes.\n */\n async function handleTfa(tfa: any, name: string, next: $NextFunctionVer): Promise<void> {\n const store = tfaStore as TfaStore;\n\n // finish enrolment: `{ tfa: ['123456'] }`\n if (Array.isArray(tfa)) {\n const [code] = tfa;\n const recoveryCodes = await store.completeEnrolment(name, String(code ?? ''));\n if (!recoveryCodes) {\n return next(errorUtils.getUnauthorized('invalid one-time password'));\n }\n return next({ tfa: recoveryCodes });\n }\n\n if (typeof tfa !== 'object' || tfa === null) {\n return next(errorUtils.getBadRequest('unsupported two-factor payload'));\n }\n\n const { mode, password } = tfa;\n if (!(await verifyPassword(name, password))) {\n return next(errorUtils.getUnauthorized(API_ERROR.BAD_USERNAME_PASSWORD));\n }\n\n if (mode === 'disable') {\n await store.disable(name);\n logger?.info({ name }, 'two-factor authentication disabled for @{name}');\n return next(buildProfile(name, false));\n }\n\n if (TFA_MODES.includes(mode) === false) {\n return next(\n errorUtils.getBadRequest(`two-factor mode must be one of ${TFA_MODES.join(', ')}`)\n );\n }\n\n const { otpauthUrl } = await store.beginEnrolment(name, mode, config.web?.title ?? 'Verdaccio');\n logger?.info({ name, mode }, 'two-factor enrolment started for @{name} in mode @{mode}');\n return next({ tfa: otpauthUrl });\n }\n\n route.post(\n PROFILE_API_ENDPOINTS.get_profile,\n rateLimit(config?.userRateLimit),\n // Turning two-factor off, switching its mode or changing the password are\n // exactly the operations a stolen session must not be able to perform on\n // its own. The middleware stays quiet while enrolment is pending, so the\n // three steps of `npm profile enable-2fa` are unaffected; `otplease` in the\n // CLI answers the challenge on the ones that follow.\n requireOtp,\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer): void {\n if (isNil(req.remote_user.name)) {\n res.status(HTTP_STATUS.UNAUTHORIZED);\n return next({\n message: API_ERROR.MUST_BE_LOGGED,\n });\n }\n\n const { password, tfa } = req.body;\n const { name } = req.remote_user;\n\n if (isNil(password) === false) {\n if (\n validationUtils.validatePassword(\n password.new,\n config?.server?.passwordValidationRegex\n ) === false\n ) {\n /* eslint new-cap:off */\n return next(errorUtils.getCode(HTTP_STATUS.UNAUTHORIZED, API_ERROR.PASSWORD_SHORT));\n }\n\n if (isEmpty(password.old)) {\n return next(errorUtils.getBadRequest('old password is required'));\n }\n\n auth.changePassword(\n name,\n password.old,\n password.new,\n (err, isUpdated): $NextFunctionVer => {\n if (err !== null) {\n return next(errorUtils.getForbidden(err.message));\n }\n\n if (isUpdated) {\n return next(buildProfile(req.remote_user.name));\n }\n return next(errorUtils.getInternalError(API_ERROR.INTERNAL_SERVER_ERROR));\n }\n );\n } else if (isNil(tfa) === false) {\n if (!tfaStore) {\n return next(\n errorUtils.getCode(HTTP_STATUS.SERVICE_UNAVAILABLE, SUPPORT_ERRORS.TFA_DISABLED)\n );\n }\n handleTfa(tfa, name, next).catch(next);\n } else {\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, APP_ERROR.PROFILE_ERROR));\n }\n }\n );\n}\n"],"mappings":"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"}
|
package/build/v1/token.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Router } from 'express';
|
|
1
|
+
import type { RequestHandler, Router } from 'express';
|
|
2
2
|
import type { Auth } from '@verdaccio/auth';
|
|
3
3
|
import type { Storage } from '@verdaccio/store';
|
|
4
4
|
import type { Config, Logger, Token } from '@verdaccio/types';
|
|
@@ -6,4 +6,6 @@ export type NormalizeToken = Token & {
|
|
|
6
6
|
cidr_whitelist: string[];
|
|
7
7
|
created: string;
|
|
8
8
|
};
|
|
9
|
-
export default function (route: Router, auth: Auth, storage: Storage, config: Config, logger: Logger
|
|
9
|
+
export default function (route: Router, auth: Auth, storage: Storage, config: Config, logger: Logger,
|
|
10
|
+
/** No-op unless the caller has two-factor enabled for this operation. */
|
|
11
|
+
requireOtp?: RequestHandler): void;
|
package/build/v1/token.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
let _verdaccio_middleware = require("@verdaccio/middleware");
|
|
2
|
-
let _verdaccio_core = require("@verdaccio/core");
|
|
3
2
|
let _verdaccio_auth = require("@verdaccio/auth");
|
|
3
|
+
let _verdaccio_core = require("@verdaccio/core");
|
|
4
4
|
let lodash_es = require("lodash-es");
|
|
5
5
|
//#region src/v1/token.ts
|
|
6
6
|
var UNSUPPORTED_TOKEN_OPTIONS = [
|
|
@@ -21,11 +21,11 @@ function normalizeToken(token) {
|
|
|
21
21
|
created: new Date(token.created).toISOString()
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
function token_default(route, auth, storage, config, logger) {
|
|
24
|
+
function token_default(route, auth, storage, config, logger, requireOtp = (_req, _res, next) => next()) {
|
|
25
25
|
route.get(_verdaccio_middleware.TOKEN_API_ENDPOINTS.get_tokens, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), async function(req, res, next) {
|
|
26
26
|
const { name } = req.remote_user;
|
|
27
27
|
if ((0, lodash_es.isNil)(name) === false) try {
|
|
28
|
-
const tokens = await storage.readTokens({ user: name });
|
|
28
|
+
const tokens = (await storage.readTokens({ user: name })).filter(({ key }) => (0, _verdaccio_auth.isReservedTokenKey)(key) === false);
|
|
29
29
|
const totalTokens = tokens.length;
|
|
30
30
|
logger.debug({ totalTokens }, "token list retrieved: @{totalTokens}");
|
|
31
31
|
res.status(_verdaccio_core.HTTP_STATUS.OK);
|
|
@@ -39,7 +39,7 @@ function token_default(route, auth, storage, config, logger) {
|
|
|
39
39
|
}
|
|
40
40
|
return next(_verdaccio_core.errorUtils.getUnauthorized());
|
|
41
41
|
});
|
|
42
|
-
route.post(_verdaccio_middleware.TOKEN_API_ENDPOINTS.get_tokens, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), function(req, res, next) {
|
|
42
|
+
route.post(_verdaccio_middleware.TOKEN_API_ENDPOINTS.get_tokens, (0, _verdaccio_middleware.rateLimit)(config?.userRateLimit), requireOtp, function(req, res, next) {
|
|
43
43
|
const { password } = req.body;
|
|
44
44
|
const { name } = req.remote_user;
|
|
45
45
|
const readonly = req.body.readonly ?? false;
|
|
@@ -101,6 +101,13 @@ function token_default(route, auth, storage, config, logger) {
|
|
|
101
101
|
const { name } = req.remote_user;
|
|
102
102
|
if ((0, lodash_es.isNil)(name) === false) {
|
|
103
103
|
logger.debug({ name }, "@{name} has requested remove a token");
|
|
104
|
+
if ((0, _verdaccio_auth.isReservedTokenKey)(tokenKey)) {
|
|
105
|
+
logger.warn({
|
|
106
|
+
tokenKey,
|
|
107
|
+
name
|
|
108
|
+
}, "refused to revoke reserved key @{tokenKey} for user @{name}");
|
|
109
|
+
return next(_verdaccio_core.errorUtils.getNotFound("token not found"));
|
|
110
|
+
}
|
|
104
111
|
try {
|
|
105
112
|
await storage.deleteToken(name, tokenKey);
|
|
106
113
|
logger.info({
|
package/build/v1/token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.js","names":[],"sources":["../../src/v1/token.ts"],"sourcesContent":["import type { Response, Router } from 'express';\nimport { isBoolean, isNil } from 'lodash-es';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport {\n HEADERS,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n cryptoUtils,\n errorUtils,\n reqUtils,\n} from '@verdaccio/core';\nimport { TOKEN_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger, RemoteUser, Token } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\n// Granular access token options (npm >= 11) that Verdaccio does not implement\n// yet. They are accepted so `npm token create` succeeds, but the restrictions\n// they express are NOT enforced — warn so the operator is not misled.\nconst UNSUPPORTED_TOKEN_OPTIONS = [\n 'packages_and_scopes_permission',\n 'packages',\n 'packages_all',\n 'scopes',\n 'orgs',\n 'orgs_permission',\n 'expires',\n 'description',\n 'bypass_2fa',\n];\n\nexport type NormalizeToken = Token & {\n cidr_whitelist: string[];\n created: string;\n};\n\n// npm expects \"cidr_whitelist\" for token list\nfunction normalizeToken(token: Token): NormalizeToken {\n return {\n ...token,\n cidr_whitelist: token.cidr || [],\n created: new Date(token.created).toISOString(),\n };\n}\n\n// https://github.com/npm/npm-profile/blob/latest/lib/index.js\nexport default function (\n route: Router,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger\n): void {\n route.get(\n TOKEN_API_ENDPOINTS.get_tokens,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {\n const { name } = req.remote_user;\n\n if (isNil(name) === false) {\n try {\n const tokens = await storage.readTokens({ user: name });\n const totalTokens = tokens.length;\n logger.debug({ totalTokens }, 'token list retrieved: @{totalTokens}');\n\n res.status(HTTP_STATUS.OK);\n return next({\n objects: tokens.map(normalizeToken),\n urls: {\n next: '', // TODO: pagination?\n },\n });\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token list has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n return next(errorUtils.getUnauthorized());\n }\n );\n\n route.post(\n TOKEN_API_ENDPOINTS.get_tokens,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {\n const { password } = req.body;\n const { name } = req.remote_user;\n // `readonly` and `cidr_whitelist` are optional: npm >= 11 rewrote\n // `npm token create` to omit them (and only sends `cidr_whitelist` with\n // `--cidr`), so default them rather than rejecting the request. A wrong\n // type is still rejected.\n const readonly = req.body.readonly ?? false;\n const cidr_whitelist = req.body.cidr_whitelist ?? [];\n\n if (!isBoolean(readonly) || !Array.isArray(cidr_whitelist)) {\n return next(errorUtils.getCode(HTTP_STATUS.BAD_DATA, SUPPORT_ERRORS.PARAMETERS_NOT_VALID));\n }\n\n const unsupportedOptions = UNSUPPORTED_TOKEN_OPTIONS.filter(\n (option) => isNil(req.body[option]) === false\n );\n if (unsupportedOptions.length > 0) {\n logger.warn(\n { options: unsupportedOptions.join(', '), userAgent: req.get('user-agent') ?? 'unknown' },\n 'granular access token options are not supported yet and will be ignored: @{options} (client: @{userAgent})'\n );\n }\n\n auth.authenticate(name, password, async (err, user?: RemoteUser) => {\n if (err) {\n const errorCode = err.message ? HTTP_STATUS.UNAUTHORIZED : HTTP_STATUS.INTERNAL_ERROR;\n return next(errorUtils.getCode(errorCode, err.message));\n }\n\n req.remote_user = user;\n\n if (typeof storage.saveToken !== 'function') {\n return next(\n errorUtils.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT)\n );\n }\n\n try {\n const key = cryptoUtils.generateRandomHexString(16);\n const token = await getApiToken(auth, config, user as RemoteUser, password, {\n tokenKey: key,\n });\n if (!token) {\n throw errorUtils.getInternalError();\n }\n\n // TODO: use a utility here\n const maskedToken = cryptoUtils.mask(token, 5);\n const created = new Date().getTime();\n\n /**\n * cidr_whitelist: is not being used, we pass it through\n * token: we do not store the real token (it is generated once and retrieved\n * to the user), just a mask of it.\n */\n const saveToken: Token = {\n user: name,\n token: maskedToken,\n key,\n cidr: cidr_whitelist,\n readonly,\n created,\n };\n\n await storage.saveToken(saveToken);\n logger.debug({ key, name }, 'token @{key} was created for user @{name}');\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n return next(\n normalizeToken({\n token,\n user: name,\n key: saveToken.key,\n cidr: cidr_whitelist,\n readonly,\n created: saveToken.created,\n })\n );\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token creation has failed: @{error}');\n return next(errorUtils.getInternalError(error.message));\n }\n });\n }\n );\n\n route.delete(\n TOKEN_API_ENDPOINTS.delete_token,\n rateLimit(config?.userRateLimit),\n async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {\n const tokenKey = reqUtils.paramToString(req.params.tokenKey);\n const { name } = req.remote_user;\n\n if (isNil(name) === false) {\n logger.debug({ name }, '@{name} has requested remove a token');\n try {\n await storage.deleteToken(name, tokenKey);\n logger.info({ tokenKey, name }, 'token id @{tokenKey} was revoked for user @{name}');\n return next({});\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token creation has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n return next(errorUtils.getUnauthorized());\n }\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"token.js","names":[],"sources":["../../src/v1/token.ts"],"sourcesContent":["import type { RequestHandler, Response, Router } from 'express';\nimport { isBoolean, isNil } from 'lodash-es';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken, isReservedTokenKey } from '@verdaccio/auth';\nimport {\n HEADERS,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n cryptoUtils,\n errorUtils,\n reqUtils,\n} from '@verdaccio/core';\nimport { TOKEN_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger, RemoteUser, Token } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\n// Granular access token options (npm >= 11) that Verdaccio does not implement\n// yet. They are accepted so `npm token create` succeeds, but the restrictions\n// they express are NOT enforced — warn so the operator is not misled.\nconst UNSUPPORTED_TOKEN_OPTIONS = [\n 'packages_and_scopes_permission',\n 'packages',\n 'packages_all',\n 'scopes',\n 'orgs',\n 'orgs_permission',\n 'expires',\n 'description',\n 'bypass_2fa',\n];\n\nexport type NormalizeToken = Token & {\n cidr_whitelist: string[];\n created: string;\n};\n\n// npm expects \"cidr_whitelist\" for token list\nfunction normalizeToken(token: Token): NormalizeToken {\n return {\n ...token,\n cidr_whitelist: token.cidr || [],\n created: new Date(token.created).toISOString(),\n };\n}\n\n// https://github.com/npm/npm-profile/blob/latest/lib/index.js\nexport default function (\n route: Router,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger,\n /** No-op unless the caller has two-factor enabled for this operation. */\n requireOtp: RequestHandler = (_req, _res, next) => next()\n): void {\n route.get(\n TOKEN_API_ENDPOINTS.get_tokens,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {\n const { name } = req.remote_user;\n\n if (isNil(name) === false) {\n try {\n // the token store doubles as a per-user key-value store; rows under a\n // reserved key are internal state, not access tokens, and listing them\n // would expose their payload through `npm token ls`\n const tokens = (await storage.readTokens({ user: name })).filter(\n ({ key }) => isReservedTokenKey(key) === false\n );\n const totalTokens = tokens.length;\n logger.debug({ totalTokens }, 'token list retrieved: @{totalTokens}');\n\n res.status(HTTP_STATUS.OK);\n return next({\n objects: tokens.map(normalizeToken),\n urls: {\n next: '', // TODO: pagination?\n },\n });\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token list has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n return next(errorUtils.getUnauthorized());\n }\n );\n\n route.post(\n TOKEN_API_ENDPOINTS.get_tokens,\n rateLimit(config?.userRateLimit),\n requireOtp,\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {\n const { password } = req.body;\n const { name } = req.remote_user;\n // `readonly` and `cidr_whitelist` are optional: npm >= 11 rewrote\n // `npm token create` to omit them (and only sends `cidr_whitelist` with\n // `--cidr`), so default them rather than rejecting the request. A wrong\n // type is still rejected.\n const readonly = req.body.readonly ?? false;\n const cidr_whitelist = req.body.cidr_whitelist ?? [];\n\n if (!isBoolean(readonly) || !Array.isArray(cidr_whitelist)) {\n return next(errorUtils.getCode(HTTP_STATUS.BAD_DATA, SUPPORT_ERRORS.PARAMETERS_NOT_VALID));\n }\n\n const unsupportedOptions = UNSUPPORTED_TOKEN_OPTIONS.filter(\n (option) => isNil(req.body[option]) === false\n );\n if (unsupportedOptions.length > 0) {\n logger.warn(\n { options: unsupportedOptions.join(', '), userAgent: req.get('user-agent') ?? 'unknown' },\n 'granular access token options are not supported yet and will be ignored: @{options} (client: @{userAgent})'\n );\n }\n\n auth.authenticate(name, password, async (err, user?: RemoteUser) => {\n if (err) {\n const errorCode = err.message ? HTTP_STATUS.UNAUTHORIZED : HTTP_STATUS.INTERNAL_ERROR;\n return next(errorUtils.getCode(errorCode, err.message));\n }\n\n req.remote_user = user;\n\n if (typeof storage.saveToken !== 'function') {\n return next(\n errorUtils.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT)\n );\n }\n\n try {\n const key = cryptoUtils.generateRandomHexString(16);\n const token = await getApiToken(auth, config, user as RemoteUser, password, {\n tokenKey: key,\n });\n if (!token) {\n throw errorUtils.getInternalError();\n }\n\n // TODO: use a utility here\n const maskedToken = cryptoUtils.mask(token, 5);\n const created = new Date().getTime();\n\n /**\n * cidr_whitelist: is not being used, we pass it through\n * token: we do not store the real token (it is generated once and retrieved\n * to the user), just a mask of it.\n */\n const saveToken: Token = {\n user: name,\n token: maskedToken,\n key,\n cidr: cidr_whitelist,\n readonly,\n created,\n };\n\n await storage.saveToken(saveToken);\n logger.debug({ key, name }, 'token @{key} was created for user @{name}');\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n return next(\n normalizeToken({\n token,\n user: name,\n key: saveToken.key,\n cidr: cidr_whitelist,\n readonly,\n created: saveToken.created,\n })\n );\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token creation has failed: @{error}');\n return next(errorUtils.getInternalError(error.message));\n }\n });\n }\n );\n\n route.delete(\n TOKEN_API_ENDPOINTS.delete_token,\n rateLimit(config?.userRateLimit),\n async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {\n const tokenKey = reqUtils.paramToString(req.params.tokenKey);\n const { name } = req.remote_user;\n\n if (isNil(name) === false) {\n logger.debug({ name }, '@{name} has requested remove a token');\n if (isReservedTokenKey(tokenKey)) {\n // this row is not a token: deleting it here would switch two-factor\n // off without the password and one-time password `npm profile\n // disable-2fa` requires\n logger.warn(\n { tokenKey, name },\n 'refused to revoke reserved key @{tokenKey} for user @{name}'\n );\n return next(errorUtils.getNotFound('token not found'));\n }\n try {\n await storage.deleteToken(name, tokenKey);\n logger.info({ tokenKey, name }, 'token id @{tokenKey} was revoked for user @{name}');\n return next({});\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token creation has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n return next(errorUtils.getUnauthorized());\n }\n );\n}\n"],"mappings":"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"}
|
package/build/v1/token.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TOKEN_API_ENDPOINTS, rateLimit } from "@verdaccio/middleware";
|
|
2
|
+
import { getApiToken, isReservedTokenKey } from "@verdaccio/auth";
|
|
2
3
|
import { HEADERS, HTTP_STATUS, SUPPORT_ERRORS, cryptoUtils, errorUtils, reqUtils } from "@verdaccio/core";
|
|
3
|
-
import { getApiToken } from "@verdaccio/auth";
|
|
4
4
|
import { isBoolean, isNil } from "lodash-es";
|
|
5
5
|
//#region src/v1/token.ts
|
|
6
6
|
var UNSUPPORTED_TOKEN_OPTIONS = [
|
|
@@ -21,11 +21,11 @@ function normalizeToken(token) {
|
|
|
21
21
|
created: new Date(token.created).toISOString()
|
|
22
22
|
};
|
|
23
23
|
}
|
|
24
|
-
function token_default(route, auth, storage, config, logger) {
|
|
24
|
+
function token_default(route, auth, storage, config, logger, requireOtp = (_req, _res, next) => next()) {
|
|
25
25
|
route.get(TOKEN_API_ENDPOINTS.get_tokens, rateLimit(config?.userRateLimit), async function(req, res, next) {
|
|
26
26
|
const { name } = req.remote_user;
|
|
27
27
|
if (isNil(name) === false) try {
|
|
28
|
-
const tokens = await storage.readTokens({ user: name });
|
|
28
|
+
const tokens = (await storage.readTokens({ user: name })).filter(({ key }) => isReservedTokenKey(key) === false);
|
|
29
29
|
const totalTokens = tokens.length;
|
|
30
30
|
logger.debug({ totalTokens }, "token list retrieved: @{totalTokens}");
|
|
31
31
|
res.status(HTTP_STATUS.OK);
|
|
@@ -39,7 +39,7 @@ function token_default(route, auth, storage, config, logger) {
|
|
|
39
39
|
}
|
|
40
40
|
return next(errorUtils.getUnauthorized());
|
|
41
41
|
});
|
|
42
|
-
route.post(TOKEN_API_ENDPOINTS.get_tokens, rateLimit(config?.userRateLimit), function(req, res, next) {
|
|
42
|
+
route.post(TOKEN_API_ENDPOINTS.get_tokens, rateLimit(config?.userRateLimit), requireOtp, function(req, res, next) {
|
|
43
43
|
const { password } = req.body;
|
|
44
44
|
const { name } = req.remote_user;
|
|
45
45
|
const readonly = req.body.readonly ?? false;
|
|
@@ -101,6 +101,13 @@ function token_default(route, auth, storage, config, logger) {
|
|
|
101
101
|
const { name } = req.remote_user;
|
|
102
102
|
if (isNil(name) === false) {
|
|
103
103
|
logger.debug({ name }, "@{name} has requested remove a token");
|
|
104
|
+
if (isReservedTokenKey(tokenKey)) {
|
|
105
|
+
logger.warn({
|
|
106
|
+
tokenKey,
|
|
107
|
+
name
|
|
108
|
+
}, "refused to revoke reserved key @{tokenKey} for user @{name}");
|
|
109
|
+
return next(errorUtils.getNotFound("token not found"));
|
|
110
|
+
}
|
|
104
111
|
try {
|
|
105
112
|
await storage.deleteToken(name, tokenKey);
|
|
106
113
|
logger.info({
|
package/build/v1/token.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token.mjs","names":[],"sources":["../../src/v1/token.ts"],"sourcesContent":["import type { Response, Router } from 'express';\nimport { isBoolean, isNil } from 'lodash-es';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken } from '@verdaccio/auth';\nimport {\n HEADERS,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n cryptoUtils,\n errorUtils,\n reqUtils,\n} from '@verdaccio/core';\nimport { TOKEN_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger, RemoteUser, Token } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\n// Granular access token options (npm >= 11) that Verdaccio does not implement\n// yet. They are accepted so `npm token create` succeeds, but the restrictions\n// they express are NOT enforced — warn so the operator is not misled.\nconst UNSUPPORTED_TOKEN_OPTIONS = [\n 'packages_and_scopes_permission',\n 'packages',\n 'packages_all',\n 'scopes',\n 'orgs',\n 'orgs_permission',\n 'expires',\n 'description',\n 'bypass_2fa',\n];\n\nexport type NormalizeToken = Token & {\n cidr_whitelist: string[];\n created: string;\n};\n\n// npm expects \"cidr_whitelist\" for token list\nfunction normalizeToken(token: Token): NormalizeToken {\n return {\n ...token,\n cidr_whitelist: token.cidr || [],\n created: new Date(token.created).toISOString(),\n };\n}\n\n// https://github.com/npm/npm-profile/blob/latest/lib/index.js\nexport default function (\n route: Router,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger\n): void {\n route.get(\n TOKEN_API_ENDPOINTS.get_tokens,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {\n const { name } = req.remote_user;\n\n if (isNil(name) === false) {\n try {\n const tokens = await storage.readTokens({ user: name });\n const totalTokens = tokens.length;\n logger.debug({ totalTokens }, 'token list retrieved: @{totalTokens}');\n\n res.status(HTTP_STATUS.OK);\n return next({\n objects: tokens.map(normalizeToken),\n urls: {\n next: '', // TODO: pagination?\n },\n });\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token list has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n return next(errorUtils.getUnauthorized());\n }\n );\n\n route.post(\n TOKEN_API_ENDPOINTS.get_tokens,\n rateLimit(config?.userRateLimit),\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {\n const { password } = req.body;\n const { name } = req.remote_user;\n // `readonly` and `cidr_whitelist` are optional: npm >= 11 rewrote\n // `npm token create` to omit them (and only sends `cidr_whitelist` with\n // `--cidr`), so default them rather than rejecting the request. A wrong\n // type is still rejected.\n const readonly = req.body.readonly ?? false;\n const cidr_whitelist = req.body.cidr_whitelist ?? [];\n\n if (!isBoolean(readonly) || !Array.isArray(cidr_whitelist)) {\n return next(errorUtils.getCode(HTTP_STATUS.BAD_DATA, SUPPORT_ERRORS.PARAMETERS_NOT_VALID));\n }\n\n const unsupportedOptions = UNSUPPORTED_TOKEN_OPTIONS.filter(\n (option) => isNil(req.body[option]) === false\n );\n if (unsupportedOptions.length > 0) {\n logger.warn(\n { options: unsupportedOptions.join(', '), userAgent: req.get('user-agent') ?? 'unknown' },\n 'granular access token options are not supported yet and will be ignored: @{options} (client: @{userAgent})'\n );\n }\n\n auth.authenticate(name, password, async (err, user?: RemoteUser) => {\n if (err) {\n const errorCode = err.message ? HTTP_STATUS.UNAUTHORIZED : HTTP_STATUS.INTERNAL_ERROR;\n return next(errorUtils.getCode(errorCode, err.message));\n }\n\n req.remote_user = user;\n\n if (typeof storage.saveToken !== 'function') {\n return next(\n errorUtils.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT)\n );\n }\n\n try {\n const key = cryptoUtils.generateRandomHexString(16);\n const token = await getApiToken(auth, config, user as RemoteUser, password, {\n tokenKey: key,\n });\n if (!token) {\n throw errorUtils.getInternalError();\n }\n\n // TODO: use a utility here\n const maskedToken = cryptoUtils.mask(token, 5);\n const created = new Date().getTime();\n\n /**\n * cidr_whitelist: is not being used, we pass it through\n * token: we do not store the real token (it is generated once and retrieved\n * to the user), just a mask of it.\n */\n const saveToken: Token = {\n user: name,\n token: maskedToken,\n key,\n cidr: cidr_whitelist,\n readonly,\n created,\n };\n\n await storage.saveToken(saveToken);\n logger.debug({ key, name }, 'token @{key} was created for user @{name}');\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n return next(\n normalizeToken({\n token,\n user: name,\n key: saveToken.key,\n cidr: cidr_whitelist,\n readonly,\n created: saveToken.created,\n })\n );\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token creation has failed: @{error}');\n return next(errorUtils.getInternalError(error.message));\n }\n });\n }\n );\n\n route.delete(\n TOKEN_API_ENDPOINTS.delete_token,\n rateLimit(config?.userRateLimit),\n async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {\n const tokenKey = reqUtils.paramToString(req.params.tokenKey);\n const { name } = req.remote_user;\n\n if (isNil(name) === false) {\n logger.debug({ name }, '@{name} has requested remove a token');\n try {\n await storage.deleteToken(name, tokenKey);\n logger.info({ tokenKey, name }, 'token id @{tokenKey} was revoked for user @{name}');\n return next({});\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token creation has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n return next(errorUtils.getUnauthorized());\n }\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"token.mjs","names":[],"sources":["../../src/v1/token.ts"],"sourcesContent":["import type { RequestHandler, Response, Router } from 'express';\nimport { isBoolean, isNil } from 'lodash-es';\n\nimport type { Auth } from '@verdaccio/auth';\nimport { getApiToken, isReservedTokenKey } from '@verdaccio/auth';\nimport {\n HEADERS,\n HTTP_STATUS,\n SUPPORT_ERRORS,\n cryptoUtils,\n errorUtils,\n reqUtils,\n} from '@verdaccio/core';\nimport { TOKEN_API_ENDPOINTS, rateLimit } from '@verdaccio/middleware';\nimport type { Storage } from '@verdaccio/store';\nimport type { Config, Logger, RemoteUser, Token } from '@verdaccio/types';\n\nimport type { $NextFunctionVer, $RequestExtend } from '../../types/custom';\n\n// Granular access token options (npm >= 11) that Verdaccio does not implement\n// yet. They are accepted so `npm token create` succeeds, but the restrictions\n// they express are NOT enforced — warn so the operator is not misled.\nconst UNSUPPORTED_TOKEN_OPTIONS = [\n 'packages_and_scopes_permission',\n 'packages',\n 'packages_all',\n 'scopes',\n 'orgs',\n 'orgs_permission',\n 'expires',\n 'description',\n 'bypass_2fa',\n];\n\nexport type NormalizeToken = Token & {\n cidr_whitelist: string[];\n created: string;\n};\n\n// npm expects \"cidr_whitelist\" for token list\nfunction normalizeToken(token: Token): NormalizeToken {\n return {\n ...token,\n cidr_whitelist: token.cidr || [],\n created: new Date(token.created).toISOString(),\n };\n}\n\n// https://github.com/npm/npm-profile/blob/latest/lib/index.js\nexport default function (\n route: Router,\n auth: Auth,\n storage: Storage,\n config: Config,\n logger: Logger,\n /** No-op unless the caller has two-factor enabled for this operation. */\n requireOtp: RequestHandler = (_req, _res, next) => next()\n): void {\n route.get(\n TOKEN_API_ENDPOINTS.get_tokens,\n rateLimit(config?.userRateLimit),\n async function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {\n const { name } = req.remote_user;\n\n if (isNil(name) === false) {\n try {\n // the token store doubles as a per-user key-value store; rows under a\n // reserved key are internal state, not access tokens, and listing them\n // would expose their payload through `npm token ls`\n const tokens = (await storage.readTokens({ user: name })).filter(\n ({ key }) => isReservedTokenKey(key) === false\n );\n const totalTokens = tokens.length;\n logger.debug({ totalTokens }, 'token list retrieved: @{totalTokens}');\n\n res.status(HTTP_STATUS.OK);\n return next({\n objects: tokens.map(normalizeToken),\n urls: {\n next: '', // TODO: pagination?\n },\n });\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token list has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n return next(errorUtils.getUnauthorized());\n }\n );\n\n route.post(\n TOKEN_API_ENDPOINTS.get_tokens,\n rateLimit(config?.userRateLimit),\n requireOtp,\n function (req: $RequestExtend, res: Response, next: $NextFunctionVer) {\n const { password } = req.body;\n const { name } = req.remote_user;\n // `readonly` and `cidr_whitelist` are optional: npm >= 11 rewrote\n // `npm token create` to omit them (and only sends `cidr_whitelist` with\n // `--cidr`), so default them rather than rejecting the request. A wrong\n // type is still rejected.\n const readonly = req.body.readonly ?? false;\n const cidr_whitelist = req.body.cidr_whitelist ?? [];\n\n if (!isBoolean(readonly) || !Array.isArray(cidr_whitelist)) {\n return next(errorUtils.getCode(HTTP_STATUS.BAD_DATA, SUPPORT_ERRORS.PARAMETERS_NOT_VALID));\n }\n\n const unsupportedOptions = UNSUPPORTED_TOKEN_OPTIONS.filter(\n (option) => isNil(req.body[option]) === false\n );\n if (unsupportedOptions.length > 0) {\n logger.warn(\n { options: unsupportedOptions.join(', '), userAgent: req.get('user-agent') ?? 'unknown' },\n 'granular access token options are not supported yet and will be ignored: @{options} (client: @{userAgent})'\n );\n }\n\n auth.authenticate(name, password, async (err, user?: RemoteUser) => {\n if (err) {\n const errorCode = err.message ? HTTP_STATUS.UNAUTHORIZED : HTTP_STATUS.INTERNAL_ERROR;\n return next(errorUtils.getCode(errorCode, err.message));\n }\n\n req.remote_user = user;\n\n if (typeof storage.saveToken !== 'function') {\n return next(\n errorUtils.getCode(HTTP_STATUS.NOT_IMPLEMENTED, SUPPORT_ERRORS.STORAGE_NOT_IMPLEMENT)\n );\n }\n\n try {\n const key = cryptoUtils.generateRandomHexString(16);\n const token = await getApiToken(auth, config, user as RemoteUser, password, {\n tokenKey: key,\n });\n if (!token) {\n throw errorUtils.getInternalError();\n }\n\n // TODO: use a utility here\n const maskedToken = cryptoUtils.mask(token, 5);\n const created = new Date().getTime();\n\n /**\n * cidr_whitelist: is not being used, we pass it through\n * token: we do not store the real token (it is generated once and retrieved\n * to the user), just a mask of it.\n */\n const saveToken: Token = {\n user: name,\n token: maskedToken,\n key,\n cidr: cidr_whitelist,\n readonly,\n created,\n };\n\n await storage.saveToken(saveToken);\n logger.debug({ key, name }, 'token @{key} was created for user @{name}');\n res.set(HEADERS.CACHE_CONTROL, HEADERS.NO_CACHE);\n return next(\n normalizeToken({\n token,\n user: name,\n key: saveToken.key,\n cidr: cidr_whitelist,\n readonly,\n created: saveToken.created,\n })\n );\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token creation has failed: @{error}');\n return next(errorUtils.getInternalError(error.message));\n }\n });\n }\n );\n\n route.delete(\n TOKEN_API_ENDPOINTS.delete_token,\n rateLimit(config?.userRateLimit),\n async (req: $RequestExtend, res: Response, next: $NextFunctionVer) => {\n const tokenKey = reqUtils.paramToString(req.params.tokenKey);\n const { name } = req.remote_user;\n\n if (isNil(name) === false) {\n logger.debug({ name }, '@{name} has requested remove a token');\n if (isReservedTokenKey(tokenKey)) {\n // this row is not a token: deleting it here would switch two-factor\n // off without the password and one-time password `npm profile\n // disable-2fa` requires\n logger.warn(\n { tokenKey, name },\n 'refused to revoke reserved key @{tokenKey} for user @{name}'\n );\n return next(errorUtils.getNotFound('token not found'));\n }\n try {\n await storage.deleteToken(name, tokenKey);\n logger.info({ tokenKey, name }, 'token id @{tokenKey} was revoked for user @{name}');\n return next({});\n } catch (error: any) {\n logger.error({ error: error.msg }, 'token creation has failed: @{error}');\n return next(errorUtils.getCode(HTTP_STATUS.INTERNAL_ERROR, error.message));\n }\n }\n return next(errorUtils.getUnauthorized());\n }\n );\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdaccio/api",
|
|
3
|
-
"version": "9.0.0-next-9.
|
|
3
|
+
"version": "9.0.0-next-9.29",
|
|
4
4
|
"description": "Verdaccio Registry API",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "build/index.d.ts",
|
|
@@ -33,23 +33,24 @@
|
|
|
33
33
|
},
|
|
34
34
|
"license": "MIT",
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@verdaccio/auth": "9.0.0-next-9.
|
|
37
|
-
"@verdaccio/config": "9.0.0-next-9.
|
|
38
|
-
"@verdaccio/core": "9.0.0-next-9.
|
|
39
|
-
"@verdaccio/hooks": "9.0.0-next-9.
|
|
40
|
-
"@verdaccio/logger": "9.0.0-next-9.
|
|
41
|
-
"@verdaccio/middleware": "9.0.0-next-9.
|
|
42
|
-
"@verdaccio/store": "9.0.0-next-9.
|
|
36
|
+
"@verdaccio/auth": "9.0.0-next-9.29",
|
|
37
|
+
"@verdaccio/config": "9.0.0-next-9.29",
|
|
38
|
+
"@verdaccio/core": "9.0.0-next-9.29",
|
|
39
|
+
"@verdaccio/hooks": "9.0.0-next-9.29",
|
|
40
|
+
"@verdaccio/logger": "9.0.0-next-9.29",
|
|
41
|
+
"@verdaccio/middleware": "9.0.0-next-9.29",
|
|
42
|
+
"@verdaccio/store": "9.0.0-next-9.29",
|
|
43
43
|
"debug": "4.4.3",
|
|
44
44
|
"express": "5.2.1",
|
|
45
45
|
"lodash-es": "4.18.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/lodash-es": "4.17.12",
|
|
49
|
-
"@verdaccio/test-helper": "5.0.0-next-9.
|
|
50
|
-
"@verdaccio/types": "14.0.0-next-9.
|
|
49
|
+
"@verdaccio/test-helper": "5.0.0-next-9.30",
|
|
50
|
+
"@verdaccio/types": "14.0.0-next-9.13",
|
|
51
51
|
"mockdate": "3.0.5",
|
|
52
52
|
"nock": "13.5.6",
|
|
53
|
+
"otpauth": "9.5.1",
|
|
53
54
|
"rimraf": "6.1.3",
|
|
54
55
|
"supertest": "7.1.4",
|
|
55
56
|
"vite": "8.2.1",
|