0
Fork 0
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:
Juan Picado 2024-05-05 16:53:28 +02:00 committed by GitHub
parent 7400830505
commit bd8703e871
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 458 additions and 409 deletions

View 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

View file

@ -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:

View file

@ -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 }}

View file

@ -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

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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);

View file

@ -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,36 +168,70 @@ 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) {
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;
debug('reusing previous key');
return secret;
}
// 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();
return this.secret;
} 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);
}
// generate a new a secret key
// FUTURE: this might be an external secret key, perhaps within config file?
debug('generating a new secret key');
this.secret = generateRandomSecretKey();
debug('generated a new secret key length %s', this.secret?.length);
debug('generated a new secret key length %s', this.secret?.length);
return this.secret;
return this.secret;
}
}
}

View file

@ -13,6 +13,7 @@ const defaultWebTokenOptions: JWTOptions = {
const defaultApiTokenConf: APITokenOptions = {
legacy: true,
migrateToSecureLegacySignature: true,
};
export const defaultSecurity: Security = {

View file

@ -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);

View file

@ -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', () => {

View file

@ -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);
}

View file

@ -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;
}

View file

@ -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();
});
});

View file

@ -39,7 +39,6 @@
},
"dependencies": {
"jsonwebtoken": "9.0.2",
"evp_bytestokey": "1.0.3",
"debug": "4.3.4"
},
"devDependencies": {

View file

@ -2,8 +2,6 @@ export {
aesDecryptDeprecated,
aesEncryptDeprecated,
generateRandomSecretKeyDeprecated,
aesDecryptDeprecatedBackwardCompatible,
aesEncryptDeprecatedBackwardCompatible,
} from './legacy-signature';
export { aesDecrypt, aesEncrypt } from './signature';

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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;

View file

@ -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));
});
});

View file

@ -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());

View file

@ -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

View file

@ -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==}

View file

@ -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

View file

@ -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.

View file

@ -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