mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-12-16 21:56:25 -05:00
feat: add migrateToSecureLegacySignature property (#4621)
* feat: add migrateToSecureLegacySignature property * Update config.ts * changeset * Update ci.yml * Update config.spec.ts
This commit is contained in:
parent
7400830505
commit
bd8703e871
28 changed files with 458 additions and 409 deletions
10
.changeset/wet-balloons-give.md
Normal file
10
.changeset/wet-balloons-give.md
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
'@verdaccio/types': minor
|
||||
'@verdaccio/core': minor
|
||||
'@verdaccio/signature': minor
|
||||
'@verdaccio/node-api': minor
|
||||
'@verdaccio/config': minor
|
||||
'@verdaccio/auth': minor
|
||||
---
|
||||
|
||||
feat: add migrateToSecureLegacySignature and remove enhancedLegacySignature property
|
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
|
@ -108,7 +108,7 @@ jobs:
|
|||
fail-fast: true
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
node_version: [18, 20, 21]
|
||||
node_version: [18, 20, 21, 22]
|
||||
name: ${{ matrix.os }} / Node ${{ matrix.node_version }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
|
|
3
.github/workflows/website.yml
vendored
3
.github/workflows/website.yml
vendored
|
@ -2,8 +2,6 @@ name: Verdaccio Website CI
|
|||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: '0 0 * * *'
|
||||
|
||||
permissions:
|
||||
contents: read # to fetch code (actions/checkout)
|
||||
|
@ -69,6 +67,7 @@ jobs:
|
|||
CONTEXT: production
|
||||
run: pnpm --filter @verdaccio/website netlify:build
|
||||
- name: Deploy to Netlify
|
||||
if: (github.event_name == 'push' && github.ref == 'refs/heads/master') || github.event_name == 'workflow_dispatch'
|
||||
env:
|
||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_SITE_ID }}
|
||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
||||
|
|
|
@ -5,12 +5,13 @@ internal features.
|
|||
|
||||
#### VERDACCIO_LEGACY_ALGORITHM
|
||||
|
||||
Allows to define the specific algorithm for the token
|
||||
signature which by default is `aes-256-ctr`
|
||||
Allows to define the specific algorithm for the token signature which by default is `aes-256-ctr`. The algorithm must be supported by `crypto.createCipheriv` and `crypto.createDecipheriv`.
|
||||
Read more here: https://nodejs.org/api/crypto.html#crypto_crypto_createcipheriv_algorithm_key_iv_options
|
||||
|
||||
#### VERDACCIO_LEGACY_ENCRYPTION_KEY
|
||||
|
||||
By default, the token stores in the database, but using this variable allows to get it from memory
|
||||
By default, the token stores in the database, but using this variable allows to get it from memory, the length must be 32 characters otherwise will throw an error.
|
||||
Read more here: https://nodejs.org/api/crypto.html#crypto_crypto_createcipheriv_algorithm_key_iv_options
|
||||
|
||||
#### VERDACCIO_PUBLIC_URL
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
pluginUtils,
|
||||
warningUtils,
|
||||
} from '@verdaccio/core';
|
||||
import '@verdaccio/core';
|
||||
import { asyncLoadPlugin } from '@verdaccio/loaders';
|
||||
import { logger } from '@verdaccio/logger';
|
||||
import {
|
||||
|
@ -21,6 +20,7 @@ import {
|
|||
aesEncryptDeprecated,
|
||||
parseBasicPayload,
|
||||
signPayload,
|
||||
utils as signatureUtils,
|
||||
} from '@verdaccio/signature';
|
||||
import {
|
||||
AllowAccess,
|
||||
|
@ -481,14 +481,9 @@ class Auth implements IAuthMiddleware, TokenEncryption, pluginUtils.IBasicAuth {
|
|||
next: Function
|
||||
): void {
|
||||
debug('handle legacy api middleware');
|
||||
debug('api middleware secret %o', typeof secret === 'string');
|
||||
debug('api middleware has a secret? %o', typeof secret === 'string');
|
||||
debug('api middleware authorization %o', typeof authorization === 'string');
|
||||
const credentials: any = getMiddlewareCredentials(
|
||||
security,
|
||||
secret,
|
||||
authorization,
|
||||
this.config?.getEnhancedLegacySignature()
|
||||
);
|
||||
const credentials: any = getMiddlewareCredentials(security, secret, authorization);
|
||||
debug('api middleware credentials %o', credentials?.name);
|
||||
if (credentials) {
|
||||
const { user, password } = credentials;
|
||||
|
@ -588,13 +583,12 @@ class Auth implements IAuthMiddleware, TokenEncryption, pluginUtils.IBasicAuth {
|
|||
* Encrypt a string.
|
||||
*/
|
||||
public aesEncrypt(value: string): string | void {
|
||||
// enhancedLegacySignature enables modern aes192 algorithm signature
|
||||
if (this.config?.getEnhancedLegacySignature()) {
|
||||
debug('signing with enhaced aes legacy');
|
||||
if (this.secret.length === signatureUtils.TOKEN_VALID_LENGTH) {
|
||||
debug('signing with enhanced aes legacy');
|
||||
const token = aesEncrypt(value, this.secret);
|
||||
return token;
|
||||
} else {
|
||||
debug('signing with enhaced aes deprecated legacy');
|
||||
debug('signing with enhanced aes deprecated legacy');
|
||||
// deprecated aes (legacy) signature, only must be used for legacy version
|
||||
const token = aesEncryptDeprecated(Buffer.from(value), this.secret).toString('base64');
|
||||
return token;
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
import buildDebug from 'debug';
|
||||
import _ from 'lodash';
|
||||
|
||||
import { TOKEN_BASIC, TOKEN_BEARER } from '@verdaccio/core';
|
||||
import { aesDecrypt, parseBasicPayload } from '@verdaccio/signature';
|
||||
import { Security } from '@verdaccio/types';
|
||||
|
||||
import { AuthMiddlewarePayload } from './types';
|
||||
import {
|
||||
convertPayloadToBase64,
|
||||
isAESLegacy,
|
||||
parseAuthTokenHeader,
|
||||
verifyJWTPayload,
|
||||
} from './utils';
|
||||
|
||||
const debug = buildDebug('verdaccio:auth:utils');
|
||||
|
||||
export function parseAESCredentials(authorizationHeader: string, secret: string) {
|
||||
debug('parseAESCredentials');
|
||||
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
|
||||
|
||||
// basic is deprecated and should not be enforced
|
||||
// basic is currently being used for functional test
|
||||
if (scheme.toUpperCase() === TOKEN_BASIC.toUpperCase()) {
|
||||
debug('legacy header basic');
|
||||
const credentials = convertPayloadToBase64(token).toString();
|
||||
|
||||
return credentials;
|
||||
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
||||
debug('legacy header bearer');
|
||||
const credentials = aesDecrypt(token, secret);
|
||||
|
||||
return credentials;
|
||||
}
|
||||
}
|
||||
|
||||
export function getMiddlewareCredentials(
|
||||
security: Security,
|
||||
secretKey: string,
|
||||
authorizationHeader: string
|
||||
): AuthMiddlewarePayload {
|
||||
debug('getMiddlewareCredentials');
|
||||
// comment out for debugging purposes
|
||||
if (isAESLegacy(security)) {
|
||||
debug('is legacy');
|
||||
const credentials = parseAESCredentials(authorizationHeader, secretKey);
|
||||
if (!credentials) {
|
||||
debug('parse legacy credentials failed');
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedCredentials = parseBasicPayload(credentials);
|
||||
if (!parsedCredentials) {
|
||||
debug('parse legacy basic payload credentials failed');
|
||||
return;
|
||||
}
|
||||
|
||||
return parsedCredentials;
|
||||
}
|
||||
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
|
||||
|
||||
debug('is jwt');
|
||||
if (_.isString(token) && scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
||||
return verifyJWTPayload(token, secretKey);
|
||||
}
|
||||
}
|
|
@ -40,12 +40,8 @@ export function parseAuthTokenHeader(authorizationHeader: string): AuthTokenHead
|
|||
return { scheme, token };
|
||||
}
|
||||
|
||||
export function parseAESCredentials(
|
||||
authorizationHeader: string,
|
||||
secret: string,
|
||||
enhanced: boolean
|
||||
) {
|
||||
debug('parseAESCredentials');
|
||||
export function parseAESCredentials(authorizationHeader: string, secret: string) {
|
||||
debug('parseAESCredentials init');
|
||||
const { scheme, token } = parseAuthTokenHeader(authorizationHeader);
|
||||
|
||||
// basic is deprecated and should not be enforced
|
||||
|
@ -57,27 +53,29 @@ export function parseAESCredentials(
|
|||
return credentials;
|
||||
} else if (scheme.toUpperCase() === TOKEN_BEARER.toUpperCase()) {
|
||||
debug('legacy header bearer');
|
||||
debug('legacy header enhanced?', enhanced);
|
||||
const credentials = enhanced
|
||||
? aesDecrypt(token.toString(), secret)
|
||||
: // FUTURE: once deprecated legacy is removed this logic won't be longer need it
|
||||
aesDecryptDeprecated(convertPayloadToBase64(token), secret).toString('utf-8');
|
||||
|
||||
return credentials;
|
||||
debug('secret length %o', secret.length);
|
||||
const isLegacyUnsecure = secret.length > 32;
|
||||
debug('is legacy unsecure %o', isLegacyUnsecure);
|
||||
if (isLegacyUnsecure) {
|
||||
debug('legacy unsecure enabled');
|
||||
return aesDecryptDeprecated(convertPayloadToBase64(token), secret).toString('utf-8');
|
||||
} else {
|
||||
debug('legacy secure enabled');
|
||||
return aesDecrypt(token.toString(), secret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function getMiddlewareCredentials(
|
||||
security: Security,
|
||||
secretKey: string,
|
||||
authorizationHeader: string,
|
||||
enhanced: boolean = true
|
||||
authorizationHeader: string
|
||||
): AuthMiddlewarePayload {
|
||||
debug('getMiddlewareCredentials');
|
||||
debug('getMiddlewareCredentials init');
|
||||
// comment out for debugging purposes
|
||||
if (isAESLegacy(security)) {
|
||||
debug('is legacy');
|
||||
const credentials = parseAESCredentials(authorizationHeader, secretKey, enhanced);
|
||||
const credentials = parseAESCredentials(authorizationHeader, secretKey);
|
||||
if (!credentials) {
|
||||
debug('parse legacy credentials failed');
|
||||
return;
|
||||
|
|
|
@ -601,16 +601,14 @@ describe('AuthTest', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('deprecated legacy handling forceEnhancedLegacySignature=false', () => {
|
||||
describe('deprecated legacy handling', () => {
|
||||
test('should handle valid auth token', async () => {
|
||||
const payload = 'juan:password';
|
||||
// const token = await signPayload(remoteUser, '12345');
|
||||
const config: Config = new AppConfig(
|
||||
{ ...authProfileConf },
|
||||
{ forceEnhancedLegacySignature: false }
|
||||
);
|
||||
const config: Config = new AppConfig({ ...authProfileConf });
|
||||
// intended to force key generator (associated with mocks above)
|
||||
config.checkSecretKey(undefined);
|
||||
// 64 characters secret long
|
||||
config.checkSecretKey('35fabdd29b820d39125e76e6d85cc294');
|
||||
const auth = new Auth(config);
|
||||
await auth.init();
|
||||
const token = auth.aesEncrypt(payload) as string;
|
||||
|
@ -624,10 +622,7 @@ describe('AuthTest', () => {
|
|||
|
||||
test('should handle invalid auth token', async () => {
|
||||
const payload = 'juan:password';
|
||||
const config: Config = new AppConfig(
|
||||
{ ...authPluginFailureConf },
|
||||
{ forceEnhancedLegacySignature: false }
|
||||
);
|
||||
const config: Config = new AppConfig({ ...authPluginFailureConf });
|
||||
// intended to force key generator (associated with mocks above)
|
||||
config.checkSecretKey(undefined);
|
||||
const auth = new Auth(config);
|
||||
|
@ -691,8 +686,7 @@ describe('AuthTest', () => {
|
|||
{
|
||||
...authProfileConf,
|
||||
...{ security: { api: { jwt: { sign: { expiresIn: '29d' } } } } },
|
||||
},
|
||||
{ forceEnhancedLegacySignature: false }
|
||||
}
|
||||
);
|
||||
// intended to force key generator (associated with mocks above)
|
||||
config.checkSecretKey(undefined);
|
||||
|
@ -700,7 +694,6 @@ describe('AuthTest', () => {
|
|||
await auth.init();
|
||||
const token = (await auth.jwtEncrypt(
|
||||
createRemoteUser('jwt_user', [ROLES.ALL]),
|
||||
// @ts-expect-error
|
||||
config.security.api.jwt.sign
|
||||
)) as string;
|
||||
const app = await getServer(auth);
|
||||
|
|
|
@ -36,6 +36,13 @@ export const defaultUserRateLimiting = {
|
|||
max: 1000,
|
||||
};
|
||||
|
||||
export function isNodeVersionGreaterThan21() {
|
||||
const [major, minor] = process.versions.node.split('.').map(Number);
|
||||
return major > 21 || (major === 21 && minor >= 0);
|
||||
}
|
||||
|
||||
const TOKEN_VALID_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* Coordinates the application configuration
|
||||
*/
|
||||
|
@ -56,21 +63,20 @@ class Config implements AppConfig {
|
|||
public plugins: string | void | null;
|
||||
public security: Security;
|
||||
public serverSettings: ServerSettingsConf;
|
||||
private configOverrideOptions: { forceMigrateToSecureLegacySignature: boolean };
|
||||
// @ts-ignore
|
||||
public secret: string;
|
||||
public flags: FlagsConfig;
|
||||
public userRateLimit: RateLimit;
|
||||
private configOptions: { forceEnhancedLegacySignature: boolean };
|
||||
public constructor(
|
||||
config: ConfigYaml & { config_path: string },
|
||||
// forceEnhancedLegacySignature is a property that
|
||||
// allows switch a new legacy aes signature token signature
|
||||
// for older versions do not want to have this new signature model
|
||||
// this property must be false
|
||||
configOptions = { forceEnhancedLegacySignature: true }
|
||||
configOverrideOptions = { forceMigrateToSecureLegacySignature: true }
|
||||
) {
|
||||
const self = this;
|
||||
this.configOptions = configOptions;
|
||||
this.storage = process.env.VERDACCIO_STORAGE_PATH || config.storage;
|
||||
if (!config.configPath) {
|
||||
// backport self_path for previous to version 6
|
||||
|
@ -80,11 +86,21 @@ class Config implements AppConfig {
|
|||
throw new Error('configPath property is required');
|
||||
}
|
||||
}
|
||||
this.configOverrideOptions = configOverrideOptions;
|
||||
this.configPath = config.configPath;
|
||||
this.self_path = this.configPath;
|
||||
debug('config path: %s', this.configPath);
|
||||
this.plugins = config.plugins;
|
||||
this.security = _.merge(defaultSecurity, config.security);
|
||||
this.security = _.merge(
|
||||
// override the default security configuration via constructor
|
||||
_.merge(defaultSecurity, {
|
||||
api: {
|
||||
migrateToSecureLegacySignature:
|
||||
this.configOverrideOptions.forceMigrateToSecureLegacySignature,
|
||||
},
|
||||
}),
|
||||
config.security
|
||||
);
|
||||
this.serverSettings = serverSettings;
|
||||
this.flags = {
|
||||
searchRemote: config.flags?.searchRemote ?? true,
|
||||
|
@ -135,14 +151,8 @@ class Config implements AppConfig {
|
|||
}
|
||||
}
|
||||
|
||||
public getEnhancedLegacySignature() {
|
||||
if (typeof this?.security.enhancedLegacySignature !== 'undefined') {
|
||||
if (this.security.enhancedLegacySignature === true) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return this.configOptions.forceEnhancedLegacySignature;
|
||||
public getMigrateToSecureLegacySignature() {
|
||||
return this.security.api.migrateToSecureLegacySignature;
|
||||
}
|
||||
|
||||
public getConfigPath() {
|
||||
|
@ -158,37 +168,71 @@ class Config implements AppConfig {
|
|||
}
|
||||
|
||||
/**
|
||||
* Store or create whether receive a secret key
|
||||
* Verify if the secret complies with the required structure
|
||||
* - If the secret is not provided, it will generate a new one
|
||||
* - For any Node.js version the new secret will be 32 characters long (to allow compatibility with modern Node.js versions)
|
||||
* - If the secret is provided:
|
||||
* - If Node.js 22 or higher, the secret must be 32 characters long thus the application will fail on startup
|
||||
* - If Node.js 21 or lower, the secret will be used as is but will display a deprecation warning
|
||||
* - If the property `security.api.migrateToSecureLegacySignature` is provided and set to true, the secret will be
|
||||
* generated with the new signature model
|
||||
* @secret external secret key
|
||||
*/
|
||||
public checkSecretKey(secret?: string): string {
|
||||
debug('check secret key');
|
||||
debug('checking secret key init');
|
||||
if (typeof secret === 'string' && _.isEmpty(secret) === false) {
|
||||
this.secret = secret;
|
||||
debug('reusing previous key');
|
||||
return secret;
|
||||
debug('checking secret key length %s', secret.length);
|
||||
if (secret.length > TOKEN_VALID_LENGTH) {
|
||||
if (isNodeVersionGreaterThan21()) {
|
||||
debug('is node version greater than 21');
|
||||
if (this.getMigrateToSecureLegacySignature() === true) {
|
||||
this.secret = generateRandomSecretKey();
|
||||
debug('rewriting secret key with length %s', this.secret.length);
|
||||
return this.secret;
|
||||
}
|
||||
// oops, user needs to generate a new secret key
|
||||
debug(
|
||||
'secret does not comply with the required length, current length %d, application will fail on startup',
|
||||
secret.length
|
||||
);
|
||||
throw new Error(
|
||||
`Invalid storage secret key length, must be 32 characters long but is ${secret.length}.
|
||||
The secret length in Node.js 22 or higher must be 32 characters long. Please consider generate a new one.
|
||||
Learn more at https://verdaccio.org/docs/configuration/#.verdaccio-db`
|
||||
);
|
||||
} else {
|
||||
debug('is node version lower than 22');
|
||||
if (this.getMigrateToSecureLegacySignature() === true) {
|
||||
this.secret = generateRandomSecretKey();
|
||||
debug('rewriting secret key with length %s', this.secret.length);
|
||||
return this.secret;
|
||||
}
|
||||
debug('triggering deprecation warning for secret key length %s', secret.length);
|
||||
// still using Node.js versions previous to 22, but we need to emit a deprecation warning
|
||||
// deprecation warning, secret key is too long and must be 32
|
||||
// this will be removed in the next major release and will produce an error
|
||||
warningUtils.emit(Codes.VERWAR007);
|
||||
this.secret = secret;
|
||||
return this.secret;
|
||||
}
|
||||
} else if (secret.length === TOKEN_VALID_LENGTH) {
|
||||
debug('detected valid secret key length %s', secret.length);
|
||||
this.secret = secret;
|
||||
return this.secret;
|
||||
}
|
||||
debug('reusing previous key with length %s', secret.length);
|
||||
this.secret = secret;
|
||||
return this.secret;
|
||||
} else {
|
||||
// generate a new a secret key
|
||||
// FUTURE: this might be an external secret key, perhaps within config file?
|
||||
debug('generating a new secret key');
|
||||
|
||||
if (this.getEnhancedLegacySignature()) {
|
||||
debug('key generated with "enhanced" legacy signature user config');
|
||||
this.secret = generateRandomSecretKey();
|
||||
} else {
|
||||
debug('key generated with legacy signature user config');
|
||||
this.secret = generateRandomHexString(32);
|
||||
}
|
||||
// set this to false allow use old token signature and is not recommended
|
||||
// only use for migration reasons, major release will remove this property and
|
||||
// set it by default
|
||||
if (this.security?.enhancedLegacySignature === false) {
|
||||
warningUtils.emit(Codes.VERWAR005);
|
||||
}
|
||||
|
||||
debug('generated a new secret key length %s', this.secret?.length);
|
||||
|
||||
return this.secret;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export { Config };
|
||||
|
|
|
@ -13,6 +13,7 @@ const defaultWebTokenOptions: JWTOptions = {
|
|||
|
||||
const defaultApiTokenConf: APITokenOptions = {
|
||||
legacy: true,
|
||||
migrateToSecureLegacySignature: true,
|
||||
};
|
||||
|
||||
export const defaultSecurity: Security = {
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import { randomBytes } from 'crypto';
|
||||
|
||||
// TODO: code duplicated at @verdaccio/signature
|
||||
export const TOKEN_VALID_LENGTH = 32;
|
||||
|
||||
/**
|
||||
* Secret key must have 32 characters.
|
||||
* // TODO: code duplicated at @verdaccio/signature
|
||||
*/
|
||||
export function generateRandomSecretKey(): string {
|
||||
return randomBytes(TOKEN_VALID_LENGTH).toString('base64').substring(0, TOKEN_VALID_LENGTH);
|
||||
|
|
|
@ -6,9 +6,12 @@ import {
|
|||
DEFAULT_REGISTRY,
|
||||
DEFAULT_UPLINK,
|
||||
ROLES,
|
||||
TOKEN_VALID_LENGTH,
|
||||
WEB_TITLE,
|
||||
defaultSecurity,
|
||||
generateRandomSecretKey,
|
||||
getDefaultConfig,
|
||||
isNodeVersionGreaterThan21,
|
||||
parseConfigFile,
|
||||
} from '../src';
|
||||
import { parseConfigurationFile } from './utils';
|
||||
|
@ -19,6 +22,8 @@ const resolveConf = (conf) => {
|
|||
return path.join(__dirname, `../src/conf/${name}${ext.startsWith('.') ? ext : '.yaml'}`);
|
||||
};
|
||||
|
||||
const itif = (condition) => (condition ? it : it.skip);
|
||||
|
||||
const checkDefaultUplink = (config) => {
|
||||
expect(_.isObject(config.uplinks[DEFAULT_UPLINK])).toBeTruthy();
|
||||
expect(config.uplinks[DEFAULT_UPLINK].url).toMatch(DEFAULT_REGISTRY);
|
||||
|
@ -94,32 +99,85 @@ describe('check basic content parsed file', () => {
|
|||
describe('checkSecretKey', () => {
|
||||
test('with default.yaml and pre selected secret', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')));
|
||||
expect(config.checkSecretKey('12345')).toEqual('12345');
|
||||
expect(config.checkSecretKey(generateRandomSecretKey())).toHaveLength(TOKEN_VALID_LENGTH);
|
||||
});
|
||||
|
||||
test('with default.yaml and void secret', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')));
|
||||
expect(typeof config.checkSecretKey() === 'string').toBeTruthy();
|
||||
const secret = config.checkSecretKey();
|
||||
expect(typeof secret === 'string').toBeTruthy();
|
||||
expect(secret).toHaveLength(TOKEN_VALID_LENGTH);
|
||||
});
|
||||
|
||||
test('with default.yaml and emtpy string secret', () => {
|
||||
test('with default.yaml and empty string secret', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')));
|
||||
expect(typeof config.checkSecretKey('') === 'string').toBeTruthy();
|
||||
const secret = config.checkSecretKey('');
|
||||
expect(typeof secret === 'string').toBeTruthy();
|
||||
expect(secret).toHaveLength(TOKEN_VALID_LENGTH);
|
||||
});
|
||||
|
||||
test('with enhanced legacy signature', () => {
|
||||
test('with default.yaml and valid string secret length', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')));
|
||||
config.security.enhancedLegacySignature = true;
|
||||
expect(typeof config.checkSecretKey() === 'string').toBeTruthy();
|
||||
expect(config.secret.length).toBe(32);
|
||||
expect(typeof config.checkSecretKey(generateRandomSecretKey()) === 'string').toBeTruthy();
|
||||
});
|
||||
|
||||
test('without enhanced legacy signature', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')));
|
||||
config.security.enhancedLegacySignature = false;
|
||||
expect(typeof config.checkSecretKey() === 'string').toBeTruthy();
|
||||
expect(config.secret.length).toBe(64);
|
||||
test('with default.yaml migrate a valid string secret length', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')), {
|
||||
forceMigrateToSecureLegacySignature: true,
|
||||
});
|
||||
expect(
|
||||
// 64 characters secret long
|
||||
config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
|
||||
).toHaveLength(TOKEN_VALID_LENGTH);
|
||||
});
|
||||
|
||||
// only runs on Node.js 22 or higher
|
||||
itif(isNodeVersionGreaterThan21())('with enhanced legacy signature Node 22 or higher', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')), {
|
||||
forceMigrateToSecureLegacySignature: false,
|
||||
});
|
||||
// eslint-disable-next-line jest/no-standalone-expect
|
||||
expect(() =>
|
||||
// 64 characters secret long
|
||||
config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
itif(isNodeVersionGreaterThan21())('with enhanced legacy signature Node 22 or higher', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')), {
|
||||
forceMigrateToSecureLegacySignature: false,
|
||||
});
|
||||
config.security.api.migrateToSecureLegacySignature = true;
|
||||
// eslint-disable-next-line jest/no-standalone-expect
|
||||
expect(
|
||||
config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
|
||||
).toHaveLength(TOKEN_VALID_LENGTH);
|
||||
});
|
||||
|
||||
itif(isNodeVersionGreaterThan21() === false)(
|
||||
'with old unsecure legacy signature Node 21 or lower',
|
||||
() => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')));
|
||||
config.security.api.migrateToSecureLegacySignature = false;
|
||||
// 64 characters secret long
|
||||
// eslint-disable-next-line jest/no-standalone-expect
|
||||
expect(
|
||||
config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
|
||||
).toHaveLength(64);
|
||||
}
|
||||
);
|
||||
|
||||
test('with migration to new legacy signature Node 21 or lower', () => {
|
||||
const config = new Config(parseConfigFile(resolveConf('default')));
|
||||
config.security.api.migrateToSecureLegacySignature = true;
|
||||
// 64 characters secret long
|
||||
// eslint-disable-next-line jest/no-standalone-expect
|
||||
expect(
|
||||
config.checkSecretKey('b4982dbb0108531fafb552374d7e83724b6458a2b3ffa97ad0edb899bdaefc4a')
|
||||
).toHaveLength(TOKEN_VALID_LENGTH);
|
||||
});
|
||||
|
||||
test.todo('test emit warning with secret key');
|
||||
});
|
||||
|
||||
describe('getMatchedPackagesSpec', () => {
|
||||
|
|
|
@ -9,17 +9,13 @@ export enum Codes {
|
|||
VERWAR002 = 'VERWAR002',
|
||||
VERWAR003 = 'VERWAR003',
|
||||
VERWAR004 = 'VERWAR004',
|
||||
VERWAR005 = 'VERWAR005',
|
||||
// deprecation warnings
|
||||
VERDEP003 = 'VERDEP003',
|
||||
VERWAR006 = 'VERWAR006',
|
||||
VERWAR007 = 'VERWAR007',
|
||||
}
|
||||
|
||||
warningInstance.create(
|
||||
verdaccioWarning,
|
||||
Codes.VERWAR002,
|
||||
`The configuration property "logs" has been deprecated, please rename to "log" for future compatibility`
|
||||
);
|
||||
/* general warnings */
|
||||
|
||||
warningInstance.create(
|
||||
verdaccioWarning,
|
||||
|
@ -27,6 +23,12 @@ warningInstance.create(
|
|||
`Verdaccio doesn't need superuser privileges. don't run it under root`
|
||||
);
|
||||
|
||||
warningInstance.create(
|
||||
verdaccioWarning,
|
||||
Codes.VERWAR002,
|
||||
`The configuration property "logs" has been deprecated, please rename to "log" for future compatibility`
|
||||
);
|
||||
|
||||
warningInstance.create(
|
||||
verdaccioWarning,
|
||||
Codes.VERWAR003,
|
||||
|
@ -42,23 +44,26 @@ https://verdaccio.org/docs/en/configuration#listen-port`
|
|||
);
|
||||
|
||||
warningInstance.create(
|
||||
verdaccioWarning,
|
||||
Codes.VERWAR005,
|
||||
'disable enhanced legacy signature is considered a security risk, please reconsider enable it'
|
||||
verdaccioDeprecation,
|
||||
Codes.VERWAR006,
|
||||
'the auth plugin method "add_user" in the auth plugin is deprecated and will be removed in next major release, rename to "adduser"'
|
||||
);
|
||||
|
||||
warningInstance.create(
|
||||
verdaccioDeprecation,
|
||||
Codes.VERWAR007,
|
||||
`the secret length is too long, it must be 32 characters long, please consider generate a new one
|
||||
Learn more at https://verdaccio.org/docs/configuration/#.verdaccio-db`
|
||||
);
|
||||
|
||||
/* deprecation warnings */
|
||||
|
||||
warningInstance.create(
|
||||
verdaccioDeprecation,
|
||||
Codes.VERDEP003,
|
||||
'multiple addresses will be deprecated in the next major, only use one'
|
||||
);
|
||||
|
||||
warningInstance.create(
|
||||
verdaccioDeprecation,
|
||||
Codes.VERWAR006,
|
||||
'the auth plugin method "add_user" in the auth plugin is deprecated and will be removed in next major release, rename to "adduser"'
|
||||
);
|
||||
|
||||
export function emit(code: string, a?: string, b?: string, c?: string) {
|
||||
warningInstance.emit(code, a, b, c);
|
||||
}
|
||||
|
|
|
@ -182,11 +182,14 @@ export interface JWTVerifyOptions {
|
|||
|
||||
export interface APITokenOptions {
|
||||
legacy: boolean;
|
||||
/**
|
||||
* Temporary flag to allow migration to the new legacy signature
|
||||
*/
|
||||
migrateToSecureLegacySignature: boolean;
|
||||
jwt?: JWTOptions;
|
||||
}
|
||||
|
||||
export interface Security {
|
||||
enhancedLegacySignature?: boolean;
|
||||
web: JWTOptions;
|
||||
api: APITokenOptions;
|
||||
}
|
||||
|
|
|
@ -15,7 +15,6 @@ describe('startServer via API', () => {
|
|||
});
|
||||
|
||||
test('should fail on start with null as entry', async () => {
|
||||
// @ts-expect-error
|
||||
await expect(runServer(null)).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -39,7 +39,6 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"jsonwebtoken": "9.0.2",
|
||||
"evp_bytestokey": "1.0.3",
|
||||
"debug": "4.3.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -2,8 +2,6 @@ export {
|
|||
aesDecryptDeprecated,
|
||||
aesEncryptDeprecated,
|
||||
generateRandomSecretKeyDeprecated,
|
||||
aesDecryptDeprecatedBackwardCompatible,
|
||||
aesEncryptDeprecatedBackwardCompatible,
|
||||
} from './legacy-signature';
|
||||
|
||||
export { aesDecrypt, aesEncrypt } from './signature';
|
||||
|
|
|
@ -1,13 +1,58 @@
|
|||
export {
|
||||
aesDecryptDeprecated,
|
||||
aesEncryptDeprecated,
|
||||
generateRandomSecretKeyDeprecated,
|
||||
TOKEN_VALID_LENGTH_DEPRECATED,
|
||||
defaultAlgorithm,
|
||||
defaultTarballHashAlgorithm,
|
||||
} from './legacy-crypto';
|
||||
// Temporary export to keep backward compatibility with Node.js >= 22
|
||||
export {
|
||||
aesDecryptDeprecatedBackwardCompatible,
|
||||
aesEncryptDeprecatedBackwardCompatible,
|
||||
} from './legacy-backward-compatible';
|
||||
import { createCipher, createDecipher } from 'crypto';
|
||||
import buildDebug from 'debug';
|
||||
|
||||
import { generateRandomHexString } from '../utils';
|
||||
|
||||
export const defaultAlgorithm = 'aes192';
|
||||
export const defaultTarballHashAlgorithm = 'sha1';
|
||||
|
||||
const debug = buildDebug('verdaccio:auth:token:legacy:deprecated');
|
||||
|
||||
/**
|
||||
*
|
||||
* @param buf
|
||||
* @param secret
|
||||
* @returns
|
||||
*/
|
||||
export function aesEncryptDeprecated(buf: Buffer, secret: string): Buffer {
|
||||
debug('aesEncryptDeprecated init');
|
||||
debug('algorithm %o', defaultAlgorithm);
|
||||
// deprecated (it will be removed in Verdaccio 6), it is a breaking change
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options
|
||||
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
|
||||
const c = createCipher(defaultAlgorithm, secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
debug('deprecated legacy token generated successfully');
|
||||
return Buffer.concat([b1, b2]);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param buf
|
||||
* @param secret
|
||||
* @returns
|
||||
*/
|
||||
export function aesDecryptDeprecated(buf: Buffer, secret: string): Buffer {
|
||||
try {
|
||||
debug('aesDecryptDeprecated init');
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createdecipher_algorithm_password_options
|
||||
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
|
||||
const c = createDecipher(defaultAlgorithm, secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
debug('deprecated legacy token payload decrypted successfully');
|
||||
return Buffer.concat([b1, b2]);
|
||||
} catch (_) {
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
}
|
||||
|
||||
export const TOKEN_VALID_LENGTH_DEPRECATED = 64;
|
||||
|
||||
/**
|
||||
* Generate a secret key of 64 characters.
|
||||
*/
|
||||
export function generateRandomSecretKeyDeprecated(): string {
|
||||
return generateRandomHexString(6);
|
||||
}
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
/* eslint-disable new-cap */
|
||||
import { createCipheriv, createDecipheriv } from 'crypto';
|
||||
import EVP_BytesToKey from 'evp_bytestokey';
|
||||
|
||||
export const defaultAlgorithm = 'aes192';
|
||||
const KEY_SIZE = 24;
|
||||
|
||||
export function aesDecryptDeprecatedBackwardCompatible(text, secret: string) {
|
||||
const result = EVP_BytesToKey(
|
||||
secret,
|
||||
null,
|
||||
KEY_SIZE * 8, // byte to bit size
|
||||
16
|
||||
);
|
||||
|
||||
let decipher = createDecipheriv(defaultAlgorithm, result.key, result.iv);
|
||||
let decrypted = decipher.update(text, 'hex', 'utf8') + decipher.final('utf8');
|
||||
return decrypted.toString();
|
||||
}
|
||||
|
||||
export function aesEncryptDeprecatedBackwardCompatible(text, secret: string) {
|
||||
const result = EVP_BytesToKey(
|
||||
secret,
|
||||
null,
|
||||
KEY_SIZE * 8, // byte to bit size
|
||||
16
|
||||
);
|
||||
|
||||
const cipher = createCipheriv(defaultAlgorithm, result.key, result.iv);
|
||||
const encrypted = cipher.update(text, 'utf8', 'hex') + cipher.final('hex');
|
||||
return encrypted.toString();
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
import { createCipher, createDecipher } from 'crypto';
|
||||
|
||||
import { generateRandomHexString } from '../utils';
|
||||
|
||||
export const defaultAlgorithm = 'aes192';
|
||||
export const defaultTarballHashAlgorithm = 'sha1';
|
||||
|
||||
/**
|
||||
* Deprecated version usage of crypto.createCipher, only useful for node.js versions < 22.
|
||||
* @param buf
|
||||
* @param secret
|
||||
* @returns
|
||||
*/
|
||||
export function aesEncryptDeprecated(buf: Buffer, secret: string): Buffer {
|
||||
// deprecated (it will be removed in Verdaccio 6), it is a breaking change
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options
|
||||
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
|
||||
const c = createCipher(defaultAlgorithm, secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deprecated version usage of crypto.createCipher, only useful for node.js versions < 22.
|
||||
* @param buf
|
||||
* @param secret
|
||||
* @returns
|
||||
*/
|
||||
export function aesDecryptDeprecated(buf: Buffer, secret: string): Buffer {
|
||||
try {
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createdecipher_algorithm_password_options
|
||||
// https://www.grainger.xyz/changing-from-cipher-to-cipheriv/
|
||||
const c = createDecipher(defaultAlgorithm, secret);
|
||||
const b1 = c.update(buf);
|
||||
const b2 = c.final();
|
||||
return Buffer.concat([b1, b2]);
|
||||
} catch (_) {
|
||||
return Buffer.alloc(0);
|
||||
}
|
||||
}
|
||||
|
||||
export const TOKEN_VALID_LENGTH_DEPRECATED = 64;
|
||||
|
||||
/**
|
||||
* Generate a secret key of 64 characters.
|
||||
*/
|
||||
export function generateRandomSecretKeyDeprecated(): string {
|
||||
return generateRandomHexString(6);
|
||||
}
|
|
@ -19,9 +19,9 @@ const outputEncoding: BinaryToTextEncoding = 'hex';
|
|||
const VERDACCIO_LEGACY_ENCRYPTION_KEY = process.env.VERDACCIO_LEGACY_ENCRYPTION_KEY;
|
||||
|
||||
export function aesEncrypt(value: string, key: string): string | void {
|
||||
debug('aesEncrypt init');
|
||||
// https://nodejs.org/api/crypto.html#crypto_crypto_createcipher_algorithm_password_options
|
||||
// https://www.grainger.xyz/posts/changing-from-cipher-to-cipheriv
|
||||
debug('encrypt %o', value);
|
||||
debug('algorithm %o', defaultAlgorithm);
|
||||
// IV must be a buffer of length 16
|
||||
const iv = randomBytes(16);
|
||||
|
@ -42,12 +42,13 @@ export function aesEncrypt(value: string, key: string): string | void {
|
|||
// @ts-ignore
|
||||
encrypted += cipher.final(outputEncoding);
|
||||
const token = `${iv.toString('hex')}:${encrypted.toString()}`;
|
||||
debug('token generated successfully');
|
||||
debug('legacy token generated successfully');
|
||||
return Buffer.from(token).toString('base64');
|
||||
}
|
||||
|
||||
export function aesDecrypt(value: string, key: string): string | void {
|
||||
try {
|
||||
debug('aesDecrypt init');
|
||||
const buff = Buffer.from(value, 'base64');
|
||||
const textParts = buff.toString().split(':');
|
||||
|
||||
|
@ -62,7 +63,7 @@ export function aesDecrypt(value: string, key: string): string | void {
|
|||
// FIXME: fix type here should allow Buffer
|
||||
let decrypted = decipher.update(encryptedText as any, outputEncoding, inputEncoding);
|
||||
decrypted += decipher.final(inputEncoding);
|
||||
debug('token decrypted successfully');
|
||||
debug('legacy token payload decrypted successfully');
|
||||
return decrypted.toString();
|
||||
} catch (_: any) {
|
||||
return;
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
import {
|
||||
aesDecryptDeprecatedBackwardCompatible,
|
||||
aesEncryptDeprecatedBackwardCompatible,
|
||||
generateRandomSecretKeyDeprecated,
|
||||
} from '../src';
|
||||
|
||||
describe('test deprecated crypto utils', () => {
|
||||
test('decrypt payload flow', () => {
|
||||
const secret = generateRandomSecretKeyDeprecated();
|
||||
const payload = 'juan:password';
|
||||
const token = aesEncryptDeprecatedBackwardCompatible(Buffer.from(payload), secret);
|
||||
const data = aesDecryptDeprecatedBackwardCompatible(token, secret);
|
||||
|
||||
expect(data.toString()).toEqual(payload.toString());
|
||||
});
|
||||
|
||||
test('crypt fails if secret is incorrect', () => {
|
||||
const payload = 'juan:password';
|
||||
expect(
|
||||
aesEncryptDeprecatedBackwardCompatible(Buffer.from(payload), 'fake_token').toString()
|
||||
).not.toEqual(Buffer.from(payload));
|
||||
});
|
||||
});
|
|
@ -1,14 +1,24 @@
|
|||
import { isNodeVersionGreaterThan21 } from '@verdaccio/config';
|
||||
|
||||
import {
|
||||
aesDecryptDeprecated,
|
||||
aesEncryptDeprecated,
|
||||
generateRandomSecretKeyDeprecated,
|
||||
} from '../src';
|
||||
|
||||
describe('test deprecated crypto utils', () => {
|
||||
const itdescribe = (condition) => (condition ? describe : describe.skip);
|
||||
|
||||
itdescribe(isNodeVersionGreaterThan21() === false)('test deprecated crypto utils', () => {
|
||||
test('generateRandomSecretKeyDeprecated', () => {
|
||||
expect(generateRandomSecretKeyDeprecated()).toHaveLength(12);
|
||||
});
|
||||
|
||||
test('decrypt payload flow', () => {
|
||||
const secret = generateRandomSecretKeyDeprecated();
|
||||
const secret = '4b4512c6ce20';
|
||||
const payload = 'juan:password';
|
||||
const token = aesEncryptDeprecated(Buffer.from(payload), secret);
|
||||
|
||||
expect(token.toString('base64')).toEqual('auizc1j3lSEd2wEB5CyGbQ==');
|
||||
const data = aesDecryptDeprecated(token, secret);
|
||||
|
||||
expect(data.toString()).toEqual(payload.toString());
|
||||
|
|
|
@ -18,9 +18,7 @@ export async function initializeServer(
|
|||
Storage
|
||||
): Promise<Application> {
|
||||
const app = express();
|
||||
// verdaccio next always uses forceEnhancedLegacySignature while legacy (5.x, 6.x)
|
||||
// have this property false by default
|
||||
const config = new Config(configName, { forceEnhancedLegacySignature: true });
|
||||
const config = new Config(configName);
|
||||
config.storage = path.join(os.tmpdir(), '/storage', generateRandomHexString());
|
||||
// httpass would get path.basename() for configPath thus we need to create a dummy folder
|
||||
// to avoid conflics
|
||||
|
|
|
@ -1602,9 +1602,6 @@ importers:
|
|||
debug:
|
||||
specifier: 4.3.4
|
||||
version: 4.3.4(supports-color@5.5.0)
|
||||
evp_bytestokey:
|
||||
specifier: 1.0.3
|
||||
version: 1.0.3
|
||||
jsonwebtoken:
|
||||
specifier: 9.0.2
|
||||
version: 9.0.2
|
||||
|
@ -17879,6 +17876,7 @@ packages:
|
|||
dependencies:
|
||||
md5.js: 1.3.5
|
||||
safe-buffer: 5.2.1
|
||||
dev: true
|
||||
|
||||
/exec-sh@0.3.6:
|
||||
resolution: {integrity: sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==}
|
||||
|
@ -19273,6 +19271,7 @@ packages:
|
|||
inherits: 2.0.4
|
||||
readable-stream: 3.6.2
|
||||
safe-buffer: 5.2.1
|
||||
dev: true
|
||||
|
||||
/hash.js@1.1.7:
|
||||
resolution: {integrity: sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==}
|
||||
|
@ -21929,6 +21928,7 @@ packages:
|
|||
hash-base: 3.1.0
|
||||
inherits: 2.0.4
|
||||
safe-buffer: 5.2.1
|
||||
dev: true
|
||||
|
||||
/mdast-squeeze-paragraphs@4.0.0:
|
||||
resolution: {integrity: sha512-zxdPn69hkQ1rm4J+2Cs2j6wDEv7O17TfXTJ33tl/+JPIoEmtV9t2ZzBM5LPHE8QlHsmVD8t3vPKCyY3oH+H8MQ==}
|
||||
|
|
|
@ -45,25 +45,28 @@ storage: ./storage
|
|||
|
||||
### The `.verdaccio-db` database {#.verdaccio-db}
|
||||
|
||||
:::info
|
||||
Only available if user does not use a custom storage
|
||||
:::
|
||||
The tiny database is used to store private packages published by the user. The database is based on a JSON file that contains
|
||||
the list of private packages published and the secret token used for the token signature.
|
||||
It is created automatically when starting the application for the first time.
|
||||
|
||||
By default verdaccio uses a little database to store private packages the `storage` property is defined in the `config.yaml` file.
|
||||
The location of the database is based on the `config.yaml` folder location, for instance:
|
||||
|
||||
The location might change based in your operative system, see [here](cli.md) more details about location of files.
|
||||
If the `config.yaml` is located in `/some_local_path/config.yaml`, the database will be created in `/some_local_path/storage/.verdaccio-db`.
|
||||
|
||||
_The `.verdaccio-db` file database is only available if user does not use a custom storage_, by default verdaccio uses a tiny database to store private packages the `storage` property is defined in the `config.yaml` file.
|
||||
The location might change based on your operating system. [Read the CLI section](cli.md) for more details about the location of files.
|
||||
|
||||
The structure of the database is based in JSON file, for instance:
|
||||
|
||||
```json
|
||||
{
|
||||
"list": ["package1", "@scope/pkg2"],
|
||||
"secret": "secret_token"
|
||||
"secret": "secret_token_32_characters_long"
|
||||
}
|
||||
```
|
||||
|
||||
- `list`: Is an array with the list of the private packages published, any item on this list is considered being published by the user.
|
||||
- `secret`: The secret field is used for verify the token signature, either for _JWT_ or legacy token signature.
|
||||
- `secret`: The secret field is used for the token signature and verification, either for _JWT_ or legacy token signature.
|
||||
|
||||
### Plugins {#plugins}
|
||||
|
||||
|
@ -86,48 +89,39 @@ auth:
|
|||
|
||||
### Token signature {#token}
|
||||
|
||||
The default token signature is based on the Advanced Encryption Standard (AES) with the algorithm `aes192`, known as _legacy_. It's important to note that legacy tokens are not designed to expire. If expiration functionality is needed, it is recommended to use _JSON Web Tokens (JWT)_ instead.
|
||||
The default token signature is based on the Advanced Encryption Standard (AES) with the algorithm `aes-256-ctr`, known as _legacy_. It's important to note that legacy tokens are not designed to expire. If expiration functionality is needed, it is recommended to use _JSON Web Tokens (JWT)_ instead.
|
||||
|
||||
#### Security {#security}
|
||||
### Security {#security}
|
||||
|
||||
The security block allows you to customise the token signature. To enable a new [JWT (JSON Web Tokens)](https://jwt.io/) signature you need to add the block `jwt` to the `api` section; `web` uses `jwt` by default.
|
||||
The security block permits customization of the token signature with two options. The configuration is divided into
|
||||
two sections, `api` and `web`. When using JWT on `api`, it must be defined; otherwise, the legacy token signature (`aes-256-ctr`) will be utilized.
|
||||
|
||||
The configuration is separated in two sections, `api` and `web`. To use JWT on `api` it has to be defined, otherwise the legacy token signature (`aes192`) will be used. For JWT you might want to customize the [signature](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) and the token [verification](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) with your own properties.
|
||||
#### How to the token is generated?
|
||||
|
||||
```
|
||||
The token signature requires a **secret token** generated by custom plugin that creates the `.verdaccio-db` database or in case a custom storage is used,
|
||||
the secret token is fetched from the plugin implementation itself. In any case the _secret token_ is required to start the application.
|
||||
|
||||
#### Legacy Token Signature
|
||||
|
||||
The `legacy` property is used to enable the legacy token signature. **By default is enabled**. The legacy feature only applies to the API, the web UI uses JWT by default.
|
||||
|
||||
```yaml
|
||||
security:
|
||||
api:
|
||||
legacy: true # by default is true even if this section is not defined
|
||||
```
|
||||
|
||||
#### JWT Token Signature
|
||||
|
||||
To enable a new [JWT (JSON Web Tokens)](https://jwt.io/) signature, the `jwt` block needs to be added to the `api` section; `jwt` is utilized by default in `web`.
|
||||
|
||||
By using the JWT signature is also possible to customize the [signature](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) and the token [verification](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) with your own properties.
|
||||
|
||||
```yaml
|
||||
security:
|
||||
enhancedLegacySignature: false
|
||||
api:
|
||||
legacy: true
|
||||
jwt:
|
||||
sign:
|
||||
expiresIn: 29d
|
||||
verify:
|
||||
someProp: [value]
|
||||
web:
|
||||
sign:
|
||||
expiresIn: 1h # 1 hour by default
|
||||
verify:
|
||||
someProp: [value]
|
||||
```
|
||||
|
||||
#### `enhancedLegacySignature` {#enhancedLegacySignature}
|
||||
|
||||
In certain instances, particularly in older installations, you might encounter the warning `[DEP0106] DeprecationWarning: crypto.createDecipher is deprecated`. in your terminal. This warning indicates that Node.js has deprecated a function utilized by the legacy signature. To address this, you can enable the enhancedLegacySignature property, which switches the legacy token signature to one based on AES-192 with an initialization vector.
|
||||
|
||||
:::caution
|
||||
|
||||
It is crucial to emphasize that enabling this option will lead to the invalidation of previous tokens.
|
||||
|
||||
In v6.x and older versions, the property `enhancedLegacySignature` is set to `false` by default upon initialization, to change that behaviour follow as illustrated in the following example.
|
||||
|
||||
:::caution
|
||||
|
||||
```
|
||||
security:
|
||||
enhancedLegacySignature: true
|
||||
api:
|
||||
legacy: true
|
||||
migrateToSecureLegacySignature: true # will generate a new secret token if the length is 64 characters
|
||||
jwt:
|
||||
sign:
|
||||
expiresIn: 29d
|
||||
|
|
|
@ -45,25 +45,61 @@ storage: ./storage
|
|||
|
||||
### The `.verdaccio-db` database {#.verdaccio-db}
|
||||
|
||||
The tiny database is used to store private packages published by the user. The database is based on a JSON file that contains
|
||||
the list of private packages published and the secret token used for the token signature.
|
||||
It is created automatically when starting the application for the first time.
|
||||
|
||||
The location of the database is based on the `config.yaml` folder location, for instance:
|
||||
|
||||
If the `config.yaml` is located in `/some_local_path/config.yaml`, the database will be created in `/some_local_path/storage/.verdaccio-db`.
|
||||
|
||||
:::info
|
||||
Only available if user does not use a custom storage
|
||||
|
||||
For users who have been using Verdaccio for an extended period and the `.verdaccio-db` file already exist the secret
|
||||
may be **64 characters** long. However, for newer installations, the length will be generated as **32 characters** long.
|
||||
|
||||
If the secret length is **64 characters** long:
|
||||
|
||||
- For users running Verdaccio 5.x on **Node.js 22** or higher, **the application will fail to start** if the secret length **is not** 32 characters long.
|
||||
- For users running Verdaccio 5.x on **Node.js 21** or lower, the application will start, but it will display a deprecation warning at the console.
|
||||
|
||||
#### How to upgrade the token secret at the storage?
|
||||
|
||||
:warning: **If the secret is updated will invalidate all previous generated tokens.**
|
||||
|
||||
##### Option 1: Manually
|
||||
|
||||
Go to the [storage location](cli.md) and edit manually the secret to be 32 characters long.
|
||||
|
||||
##### Option 2: Automatically (since v5.30.3)
|
||||
|
||||
The `migrateToSecureLegacySignature` property is used to generate a new secret token if the length is 64 characters.
|
||||
|
||||
```
|
||||
security:
|
||||
api:
|
||||
migrateToSecureLegacySignature: true
|
||||
```
|
||||
|
||||
The token will be automatically updated to 32 characters long and the application will start without any issues.
|
||||
The property won't have any other effect on the application and could be removed after the secret is updated.
|
||||
|
||||
:::
|
||||
|
||||
By default verdaccio uses a little database to store private packages the `storage` property is defined in the `config.yaml` file.
|
||||
|
||||
The location might change based in your operative system, see [here](cli.md) more details about location of files.
|
||||
_The `.verdaccio-db` file database is only available if user does not use a custom storage_, by default verdaccio uses a tiny database to store private packages the `storage` property is defined in the `config.yaml` file.
|
||||
The location might change based on your operating system. [Read the CLI section](cli.md) for more details about the location of files.
|
||||
|
||||
The structure of the database is based in JSON file, for instance:
|
||||
|
||||
```json
|
||||
{
|
||||
"list": ["package1", "@scope/pkg2"],
|
||||
"secret": "secret_token"
|
||||
"secret": "secret_token_32_characters_long"
|
||||
}
|
||||
```
|
||||
|
||||
- `list`: Is an array with the list of the private packages published, any item on this list is considered being published by the user.
|
||||
- `secret`: The secret field is used for verify the token signature, either for _JWT_ or legacy token signature.
|
||||
- `secret`: The secret field is used for the token signature and verification, either for _JWT_ or legacy token signature.
|
||||
|
||||
### Plugins {#plugins}
|
||||
|
||||
|
@ -86,14 +122,47 @@ auth:
|
|||
|
||||
### Security {#security}
|
||||
|
||||
The security block allows you to customise the token signature. To enable a new [JWT (JSON Web Tokens)](https://jwt.io/) signature you need to add the block `jwt` to the `api` section; `web` uses `jwt` by default.
|
||||
The security block permits customization of the token signature with two options. The configuration is divided into
|
||||
two sections, `api` and `web`. When using JWT on `api`, it must be defined; otherwise, the legacy token signature (`aes192`) will be utilized.
|
||||
|
||||
The configuration is separated in two sections, `api` and `web`. To use JWT on `api` it has to be defined, otherwise the legacy token signature (`aes192`) will be used. For JWT you might want to customize the [signature](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) and the token [verification](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) with your own properties.
|
||||
#### How to the token is generated?
|
||||
|
||||
The token signature requires a **secret token** generated by custom plugin that creates the `.verdaccio-db` database or in case a custom storage is used,
|
||||
the secret token is fetched from the plugin implementation itself. In any case the _secret token_ is required to start the application.
|
||||
|
||||
#### Legacy Token Signature
|
||||
|
||||
The `legacy` property is used to enable the legacy token signature. **By default is enabled**. The legacy feature only applies to the API, the web UI uses JWT by default.
|
||||
|
||||
:::info
|
||||
|
||||
In 5.x versions using Node.js 21 or lower, there will see the warning `[DEP0106] DeprecationWarning: crypto.createDecipher is deprecated`. printed in your terminal.
|
||||
This warning indicates that Node.js has deprecated a function utilized by the legacy signature.
|
||||
|
||||
If verdaccio runs on **Node.js 22** or higher, you will not see this warning since a new modern legacy signature has been implemented.
|
||||
|
||||
The **migrateToSecureLegacySignature** property is only available for versions higher than 5.30.3 and is **false** by default.
|
||||
|
||||
:::info
|
||||
|
||||
```yaml
|
||||
security:
|
||||
api:
|
||||
legacy: true # by default is true even if this section is not defined
|
||||
migrateToSecureLegacySignature: true # will generate a new secret token if the length is 64 characters
|
||||
```
|
||||
|
||||
#### JWT Token Signature
|
||||
|
||||
To enable a new [JWT (JSON Web Tokens)](https://jwt.io/) signature, the `jwt` block needs to be added to the `api` section; `jwt` is utilized by default in `web`.
|
||||
|
||||
By using the JWT signature is also possible to customize the [signature](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) and the token [verification](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) with your own properties.
|
||||
|
||||
```yaml
|
||||
security:
|
||||
api:
|
||||
legacy: true
|
||||
migrateToSecureLegacySignature: true # will generate a new secret token if the length is 64 characters
|
||||
jwt:
|
||||
sign:
|
||||
expiresIn: 29d
|
||||
|
@ -106,17 +175,11 @@ security:
|
|||
someProp: [value]
|
||||
```
|
||||
|
||||
:::info
|
||||
|
||||
In 5.x versions, you will see the warning `[DEP0106] DeprecationWarning: crypto.createDecipher is deprecated`. in your terminal. This warning indicates that Node.js has deprecated a function utilized by the legacy signature. **To address thi please upgrade to the newest 6.x version (to check the release status read here [available](https://github.com/verdaccio/verdaccio/discussions/4018)).**
|
||||
|
||||
:::info
|
||||
|
||||
### Server {#server}
|
||||
|
||||
A set of properties to modify the behavior of the server application, specifically the API (Express.js).
|
||||
|
||||
> You can specify HTTP/1.1 server keep alive timeout in seconds for incomming connections.
|
||||
> You can specify HTTP/1.1 server keep alive timeout in seconds for incoming connections.
|
||||
> A value of 0 makes the http server behave similarly to Node.js versions prior to 8.0.0, which did not have a keep-alive timeout.
|
||||
> WORKAROUND: Through given configuration you can workaround following issue https://github.com/verdaccio/verdaccio/issues/301. Set to 0 in case 60 is not enough.
|
||||
|
||||
|
|
|
@ -45,25 +45,28 @@ storage: ./storage
|
|||
|
||||
### The `.verdaccio-db` database {#.verdaccio-db}
|
||||
|
||||
:::info
|
||||
Only available if user does not use a custom storage
|
||||
:::
|
||||
The tiny database is used to store private packages published by the user. The database is based on a JSON file that contains
|
||||
the list of private packages published and the secret token used for the token signature.
|
||||
It is created automatically when starting the application for the first time.
|
||||
|
||||
By default verdaccio uses a little database to store private packages the `storage` property is defined in the `config.yaml` file.
|
||||
The location of the database is based on the `config.yaml` folder location, for instance:
|
||||
|
||||
The location might change based in your operative system, see [here](cli.md) more details about location of files.
|
||||
If the `config.yaml` is located in `/some_local_path/config.yaml`, the database will be created in `/some_local_path/storage/.verdaccio-db`.
|
||||
|
||||
_The `.verdaccio-db` file database is only available if user does not use a custom storage_, by default verdaccio uses a tiny database to store private packages the `storage` property is defined in the `config.yaml` file.
|
||||
The location might change based on your operating system. [Read the CLI section](cli.md) for more details about the location of files.
|
||||
|
||||
The structure of the database is based in JSON file, for instance:
|
||||
|
||||
```json
|
||||
{
|
||||
"list": ["package1", "@scope/pkg2"],
|
||||
"secret": "secret_token"
|
||||
"secret": "secret_token_32_characters_long"
|
||||
}
|
||||
```
|
||||
|
||||
- `list`: Is an array with the list of the private packages published, any item on this list is considered being published by the user.
|
||||
- `secret`: The secret field is used for verify the token signature, either for _JWT_ or legacy token signature.
|
||||
- `secret`: The secret field is used for the token signature and verification, either for _JWT_ or legacy token signature.
|
||||
|
||||
### Plugins {#plugins}
|
||||
|
||||
|
@ -86,48 +89,50 @@ auth:
|
|||
|
||||
### Token signature {#token}
|
||||
|
||||
The default token signature is based on the Advanced Encryption Standard (AES) with the algorithm `aes192`, known as _legacy_. It's important to note that legacy tokens are not designed to expire. If expiration functionality is needed, it is recommended to use _JSON Web Tokens (JWT)_ instead.
|
||||
The default token signature is based on the Advanced Encryption Standard (AES) with the algorithm `aes-256-ctr`, known as _legacy_.
|
||||
It's important to note that legacy tokens are not designed to expire.
|
||||
If expiration functionality is needed, it is recommended to use _JSON Web Tokens (JWT)_ instead.
|
||||
|
||||
#### Security {#security}
|
||||
|
||||
The security block allows you to customise the token signature. To enable a new [JWT (JSON Web Tokens)](https://jwt.io/) signature you need to add the block `jwt` to the `api` section; `web` uses `jwt` by default.
|
||||
The security block permits customization of the token signature with two options. The configuration is divided into
|
||||
two sections, `api` and `web`. When using JWT on `api`, it must be defined; otherwise, the legacy token signature (`aes192`) will be utilized.
|
||||
|
||||
The configuration is separated in two sections, `api` and `web`. To use JWT on `api` it has to be defined, otherwise the legacy token signature (`aes192`) will be used. For JWT you might want to customize the [signature](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) and the token [verification](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) with your own properties.
|
||||
#### How to the token is generated?
|
||||
|
||||
```
|
||||
The token signature requires a **secret token** generated by custom plugin that creates the `.verdaccio-db` database or in case a custom storage is used,
|
||||
the secret token is fetched from the plugin implementation itself. In any case the _secret token_ is required to start the application.
|
||||
|
||||
#### Legacy Token Signature
|
||||
|
||||
The `legacy` property is used to enable the legacy token signature. **By default is enabled**. The legacy feature only applies to the API, the web UI uses JWT by default.
|
||||
|
||||
:::info
|
||||
|
||||
The **migrateToSecureLegacySignature** property is **true** by default on 6.x versions or higher and could be disabled but is not recommended otherwise will cause issues using Node.js 22 or higher.
|
||||
|
||||
:::info
|
||||
|
||||
```yaml
|
||||
# This configuration is the default one only displayed for reference,
|
||||
# no need to define it in your config file.
|
||||
security:
|
||||
api:
|
||||
legacy: true # by default is true even if this section is not defined
|
||||
migrateToSecureLegacySignature: true # will generate a new secret token if the length is 64 characters
|
||||
```
|
||||
|
||||
#### JWT Token Signature
|
||||
|
||||
To enable a new [JWT (JSON Web Tokens)](https://jwt.io/) signature, the `jwt` block needs to be added to the `api` section; `jwt` is utilized by default in `web`.
|
||||
|
||||
By using the JWT signature is also possible to customize the [signature](https://github.com/auth0/node-jsonwebtoken#jwtsignpayload-secretorprivatekey-options-callback) and the token [verification](https://github.com/auth0/node-jsonwebtoken#jwtverifytoken-secretorpublickey-options-callback) with your own properties.
|
||||
|
||||
```yaml
|
||||
security:
|
||||
enhancedLegacySignature: false
|
||||
api:
|
||||
legacy: true
|
||||
jwt:
|
||||
sign:
|
||||
expiresIn: 29d
|
||||
verify:
|
||||
someProp: [value]
|
||||
web:
|
||||
sign:
|
||||
expiresIn: 1h # 1 hour by default
|
||||
verify:
|
||||
someProp: [value]
|
||||
```
|
||||
|
||||
#### `enhancedLegacySignature` {#enhancedLegacySignature}
|
||||
|
||||
In certain instances, particularly in older installations, you might encounter the warning `[DEP0106] DeprecationWarning: crypto.createDecipher is deprecated`. in your terminal. This warning indicates that Node.js has deprecated a function utilized by the legacy signature. To address this, you can enable the `enhancedLegacySignature` property, which switches the legacy token signature to one based on AES-192 with an initialization vector.
|
||||
|
||||
:::caution
|
||||
|
||||
It is crucial to emphasize that enabling this option will lead to the invalidation of previous tokens.
|
||||
|
||||
For all 6.x versions, the property `enhancedLegacySignature` is set to `false` by default upon initialization, to change that behaviour follow as illustrated in the following example.
|
||||
|
||||
:::caution
|
||||
|
||||
```
|
||||
security:
|
||||
enhancedLegacySignature: true
|
||||
api:
|
||||
legacy: true
|
||||
migrateToSecureLegacySignature: true # will generate a new secret token if the length is 64 characters
|
||||
jwt:
|
||||
sign:
|
||||
expiresIn: 29d
|
||||
|
|
Loading…
Reference in a new issue