diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 9af07cd6..744ee2fc 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node-version: [18, 20, 21]
+ node-version: [18, 20, 22]
os: [ubuntu-latest]
steps:
@@ -83,7 +83,7 @@ jobs:
strategy:
fail-fast: false
matrix:
- node-version: [16, 18, 20]
+ node-version: [18, 20, 22]
os: [ubuntu-latest]
steps:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index c8ef203c..2847e460 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -10,5 +10,3 @@ jobs:
secrets:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
- with:
- checkTest: false
diff --git a/app/common/CryptoUtil.ts b/app/common/CryptoUtil.ts
index 478c5c59..4e9e7ceb 100644
--- a/app/common/CryptoUtil.ts
+++ b/app/common/CryptoUtil.ts
@@ -1,4 +1,5 @@
-import { generateKeyPairSync, publicEncrypt, privateDecrypt, constants } from 'crypto';
+import { generateKeyPairSync } from 'crypto';
+import NodeRSA from 'node-rsa';
// generate rsa key pair
export function genRSAKeys(): { publicKey: string, privateKey: string } {
@@ -17,17 +18,19 @@ export function genRSAKeys(): { publicKey: string, privateKey: string } {
}
// encrypt rsa private key
-export function encryptRSA(publicKey: string, data: string): string {
- return publicEncrypt({
- key: publicKey,
- padding: constants.RSA_PKCS1_PADDING,
- }, Buffer.from(data, 'utf8')).toString('base64');
+export function encryptRSA(publicKey: string, plainText: string): string {
+ const key = new NodeRSA(publicKey, 'pkcs1-public-pem', {
+ encryptionScheme: 'pkcs1',
+ environment: 'browser',
+ });
+ return key.encrypt(plainText, 'base64');
}
// decrypt rsa private key
-export function decryptRSA(privateKey: string, data: string) {
- return privateDecrypt({
- key: privateKey,
- padding: constants.RSA_PKCS1_PADDING,
- }, Buffer.from(data, 'base64')).toString('utf8');
+export function decryptRSA(privateKey: string, encryptedBase64: string): string {
+ const key = new NodeRSA(privateKey, 'pkcs1-private-pem', {
+ encryptionScheme: 'pkcs1',
+ environment: 'browser',
+ });
+ return key.decrypt(encryptedBase64, 'utf8');
}
diff --git a/package.json b/package.json
index e8e6665e..6bfea69b 100644
--- a/package.json
+++ b/package.json
@@ -60,9 +60,6 @@
"url": "git@github.com:cnpm/cnpmcore.git"
},
"egg": {
- "revert": [
- "CVE-2023-46809"
- ],
"typescript": true
},
"keywords": [
@@ -105,6 +102,7 @@
"lodash": "^4.17.21",
"mime-types": "^2.1.35",
"mysql2": "^3.9.4",
+ "node-rsa": "^1.1.1",
"npm-package-arg": "^10.1.0",
"oss-cnpm": "^5.0.1",
"p-map": "^4.0.0",
@@ -126,6 +124,7 @@
"@types/mime-types": "^2.1.1",
"@types/mocha": "^10.0.1",
"@types/mysql": "^2.15.21",
+ "@types/node-rsa": "^1.1.4",
"@types/npm-package-arg": "^6.1.1",
"@types/semver": "^7.3.12",
"@types/tar": "^6.1.4",
diff --git a/test/common/CryptoUtil.test.ts b/test/common/CryptoUtil.test.ts
new file mode 100644
index 00000000..ba5fd076
--- /dev/null
+++ b/test/common/CryptoUtil.test.ts
@@ -0,0 +1,22 @@
+import { strict as assert } from 'node:assert';
+import { genRSAKeys, encryptRSA, decryptRSA } from '../../app/common/CryptoUtil';
+
+describe('test/common/CryptoUtil.test.ts', () => {
+ describe('genRSAKeys()', () => {
+ it('should work', () => {
+ const keys = genRSAKeys();
+ assert(keys.publicKey);
+ assert(keys.privateKey);
+ });
+ });
+
+ describe('encryptRSA(), decryptRSA()', () => {
+ it('should work', () => {
+ const keys = genRSAKeys();
+ // const plainText = 'hello world 中文😄';
+ const plainText = 'hello world 中文';
+ const encryptText = encryptRSA(keys.publicKey, plainText);
+ assert.equal(decryptRSA(keys.privateKey, encryptText), plainText);
+ });
+ });
+});
diff --git a/test/port/webauth/webauthController.test.ts b/test/port/webauth/webauthController.test.ts
index 61724e3b..aef0720c 100644
--- a/test/port/webauth/webauthController.test.ts
+++ b/test/port/webauth/webauthController.test.ts
@@ -1,6 +1,6 @@
-import assert from 'assert';
-import crypto from 'crypto';
-import { basename } from 'path';
+import { strict as assert } from 'node:assert';
+import crypto from 'node:crypto';
+import { basename } from 'node:path';
import { app, mock } from 'egg-mock/bootstrap';
import { AuthAdapter } from '../../../app/infra/AuthAdapter';
import { CacheAdapter } from '../../../app/common/adapter/CacheAdapter';
@@ -32,13 +32,10 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 422);
assert.equal(res.body.error, "[INVALID_PARAM] must have required property 'hostname'");
-
});
-
});
describe('GET /-/v1/login/request/session/:sessionId', () => {
-
let sessionId = '';
const rsaKeys = genRSAKeys();
beforeEach(async () => {
@@ -48,14 +45,12 @@ describe('test/port/webauth/webauthController.test.ts', () => {
await cacheAdapter.set(`${sessionId}_privateKey`, rsaKeys.privateKey);
});
-
it('should check sessionId type', async () => {
const res = await app.httpRequest()
.get('/-/v1/login/request/session/123');
assert.equal(res.status, 422);
assert.equal(res.body.error, '[INVALID_PARAM] sessionId: must NOT have fewer than 36 characters');
-
});
it('should check sessionId exists', async () => {
@@ -65,7 +60,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 404);
assert(/Session not found/.test(res.text));
assert.equal(res.headers['content-type'], 'text/html; charset=utf-8');
-
});
it('should render login.html', async () => {
@@ -74,13 +68,10 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 200);
assert(/
Sign in to CNPM<\/title>/.test(res.text));
-
});
-
});
describe('POST /-/v1/login/request/session/:sessionId', () => {
-
let sessionId = '';
const rsaKeys = genRSAKeys();
beforeEach(async () => {
@@ -90,14 +81,12 @@ describe('test/port/webauth/webauthController.test.ts', () => {
await cacheAdapter.set(`${sessionId}_privateKey`, rsaKeys.privateKey);
});
-
it('should check sessionId type', async () => {
const res = await app.httpRequest()
.post('/-/v1/login/request/session/123');
assert.equal(res.status, 422);
assert.equal(res.body.error, '[INVALID_PARAM] sessionId: must NOT have fewer than 36 characters');
-
});
it('should check sessionId exists', async () => {
@@ -107,7 +96,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 200);
assert(/Session not found/.test(res.text));
assert.equal(res.headers['content-type'], 'application/json; charset=utf-8');
-
});
describe('should verify login request body', () => {
@@ -122,7 +110,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
});
it('should login success', async () => {
-
const password = encryptRSA(rsaKeys.publicKey, 'flymetothemoon');
const res = await app.httpRequest()
.post(`/-/v1/login/request/session/${sessionId}`)
@@ -138,7 +125,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
});
it('should check password', async () => {
-
const password = encryptRSA(rsaKeys.publicKey, 'incorrect_password');
const res = await app.httpRequest()
.post(`/-/v1/login/request/session/${sessionId}`)
@@ -151,11 +137,9 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 200);
assert(/Please check your login name and password/.test(res.body.message));
-
});
it('should check user params', async () => {
-
const password = encryptRSA(rsaKeys.publicKey, 'incorrect_password');
const res = await app.httpRequest()
.post(`/-/v1/login/request/session/${sessionId}`)
@@ -168,7 +152,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 200);
assert(/Unauthorized, Validation Failed/.test(res.body.message));
-
});
it('should check authentication user (unbound webauthn) when enableWebauthn', async () => {
@@ -222,11 +205,8 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 200);
assert(/Public registration is not allowed/.test(res.body.message));
-
});
-
});
-
});
describe('/-/v1/login/request/prepare/:sessionId', () => {
@@ -256,7 +236,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 422);
assert.equal(res.body.error, '[INVALID_PARAM] sessionId: must NOT have fewer than 36 characters');
-
});
it('should check sessionId exists', async () => {
@@ -266,11 +245,9 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 200);
assert(/Session not found/.test(res.text));
assert.equal(res.headers['content-type'], 'application/json; charset=utf-8');
-
});
it('should get prepare with authentication options', async () => {
-
const res = await app.httpRequest()
.get(`/-/v1/login/request/prepare/${sessionId}?name=banana`);
@@ -279,7 +256,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
});
it('should get prepare with registration options', async () => {
-
const res = await app.httpRequest()
.get(`/-/v1/login/request/prepare/${sessionId}?name=apple`);
@@ -289,7 +265,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
});
describe('/-/v1/login/sso/:sessionId', () => {
-
let sessionId = '';
beforeEach(async () => {
sessionId = crypto.randomUUID();
@@ -304,7 +279,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
});
it('should sso login work', async () => {
-
const res = await app.httpRequest()
.post(`/-/v1/login/sso/${sessionId}`);
@@ -312,7 +286,6 @@ describe('test/port/webauth/webauthController.test.ts', () => {
});
it('should check sessionId exists', async () => {
-
const res = await app.httpRequest()
.post('/-/v1/login/sso/banana');
@@ -340,25 +313,20 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 403);
assert.equal(res.body.error, '[FORBIDDEN] invalid user info');
});
-
});
describe('/-/v1/login/request/success', () => {
-
it('should work', async () => {
-
const res = await app.httpRequest()
.get('/-/v1/login/request/success');
assert.equal(res.status, 200);
assert.equal(res.headers['content-type'], 'text/html; charset=utf-8');
assert(/Authorization Successful/.test(res.text));
-
});
});
describe('/-/v1/login/done/session/:sessionId', () => {
-
let sessionId = '';
beforeEach(async () => {
sessionId = crypto.randomUUID();
@@ -368,38 +336,31 @@ describe('test/port/webauth/webauthController.test.ts', () => {
it('should check sessionId type', async () => {
-
const res = await app.httpRequest()
.get('/-/v1/login/done/session/123');
assert.equal(res.status, 422);
assert.equal(res.body.error, '[INVALID_PARAM] sessionId: must NOT have fewer than 36 characters');
-
});
it('should check sessionId exists', async () => {
-
const res = await app.httpRequest()
.get(`/-/v1/login/done/session/${crypto.randomUUID()}`);
assert.equal(res.status, 404);
assert.equal(res.body.error, '[NOT_FOUND] session not found');
-
});
it('should re-validate sessionId', async () => {
-
const res = await app.httpRequest()
.get(`/-/v1/login/done/session/${sessionId}`);
assert.equal(res.status, 202);
assert.equal(res.body.message, 'processing');
- assert.equal(res.headers['retry-after'], 1);
-
+ assert.equal(res.headers['retry-after'], '1');
});
it('should check sessionId exists', async () => {
-
const cacheAdapter = await app.getEggObject(CacheAdapter);
await cacheAdapter.set(sessionId, 'banana');
const res = await app.httpRequest()
@@ -407,9 +368,7 @@ describe('test/port/webauth/webauthController.test.ts', () => {
assert.equal(res.status, 200);
assert.equal(res.body.token, 'banana');
-
- assert(await cacheAdapter.get(sessionId) === null);
-
+ assert.equal(await cacheAdapter.get(sessionId), null);
});
});
});