cnpmcore
Version:
Private NPM Registry for Enterprise
414 lines • 33.8 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
import { createHash } from 'node:crypto';
import '@eggjs/typebox-validate';
import { Type } from '@eggjs/typebox-validate/typebox';
import { generateAuthenticationOptions, generateRegistrationOptions, verifyAuthenticationResponse, verifyRegistrationResponse, } from '@simplewebauthn/server';
import base64url from 'base64url';
import { HTTPContext, HTTPBody, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam, HTTPQuery, Inject, } from 'egg';
import { ForbiddenError, NotFoundError } from 'egg/errors';
import { decryptRSA, genRSAKeys } from "../../common/CryptoUtil.js";
import { LoginResultCode, WanStatusCode } from "../../common/enum/User.js";
import { getBrowserTypeForWebauthn } from "../../common/UserUtil.js";
import { MiddlewareController } from "../middleware/index.js";
const LoginRequestRule = Type.Object({
// cli 所在机器的 hostname,最新版本 npm cli 已经不会上报 hostname
hostname: Type.Optional(Type.String({ minLength: 1, maxLength: 100 })),
});
const UserRule = Type.Object({
name: Type.String({ minLength: 1, maxLength: 100 }),
password: Type.String({ minLength: 8, maxLength: 100 }),
});
const SessionRule = Type.Object({
// uuid
sessionId: Type.String({ minLength: 36, maxLength: 36 }),
});
let WebauthController = class WebauthController extends MiddlewareController {
// https://github.com/cnpm/cnpmcore/issues/348
async login(ctx, loginRequest) {
ctx.tValidate(LoginRequestRule, loginRequest);
return this.authAdapter.getAuthUrl(ctx);
}
async loginRender(ctx, sessionId) {
ctx.tValidate(SessionRule, { sessionId });
ctx.type = 'html';
const sessionToken = await this.cacheAdapter.get(sessionId);
if (typeof sessionToken !== 'string') {
ctx.status = 404;
return '<h1>😭😭😭 Session not found, please try again on your command line 😭😭😭</h1>';
}
const keys = genRSAKeys();
await this.cacheAdapter.set(`${sessionId}_privateKey`, keys.privateKey);
await ctx.render('login.html', {
sessionId,
publicKey: keys.publicKey,
enableWebauthn: this.config.cnpmcore.enableWebAuthn,
});
}
async loginImplement(ctx, sessionId, loginImplementRequest) {
ctx.tValidate(SessionRule, { sessionId });
const sessionToken = await this.cacheAdapter.get(sessionId);
if (typeof sessionToken !== 'string') {
return {
ok: false,
message: 'Session not found, please try again on your command line',
};
}
const { accData, wanCredentialRegiData, wanCredentialAuthData, needUnbindWan } = loginImplementRequest;
const { username, password = '' } = accData;
const enableWebAuthn = this.config.cnpmcore.enableWebAuthn;
const isSupportWebAuthn = ctx.protocol === 'https' || ctx.hostname === 'localhost';
let token = '';
let user;
// public registration
if (this.config.cnpmcore.allowPublicRegistration === false && !this.config.cnpmcore.admins[username]) {
return { ok: false, message: 'Public registration is not allowed' };
}
const browserType = getBrowserTypeForWebauthn(ctx.headers['user-agent']) || undefined;
const expectedChallenge = (await this.cacheAdapter.get(`${sessionId}_challenge`)) || '';
const expectedOrigin = this.config.cnpmcore.registry;
const expectedRPID = new URL(expectedOrigin).hostname;
// webauthn authentication
if (enableWebAuthn && isSupportWebAuthn && wanCredentialAuthData) {
user = await this.userService.findUserByName(username);
if (!user) {
return {
ok: false,
message: 'Unauthorized, Please check your login name',
};
}
const credential = await this.userService.findWebauthnCredential(user.userId, browserType);
if (!credential?.credentialId || !credential?.publicKey) {
return {
ok: false,
message: 'Unauthorized, Please check your login name',
};
}
try {
const verification = await verifyAuthenticationResponse({
response: wanCredentialAuthData,
expectedChallenge,
expectedOrigin,
expectedRPID,
authenticator: {
// @ts-expect-error type error
credentialPublicKey: base64url.toBuffer(credential.publicKey),
// @ts-expect-error type error
credentialID: base64url.toBuffer(credential.credentialId),
counter: 0,
},
});
const { verified } = verification;
if (!verified) {
return {
ok: false,
message: 'Invalid security arguments, please try again on your browser',
};
}
}
catch (err) {
this.logger.error('[WebauthController.loginImplement:verify-authentication-fail] expectedChallenge: %s, expectedOrigin: %s, expectedRPID: %s, wanCredentialAuthData: %j, error: %j', expectedChallenge, expectedOrigin, expectedRPID, wanCredentialAuthData, err);
return {
ok: false,
message: 'Authentication failed, please continue to sign in with your password',
};
}
const createToken = await this.userService.createToken(user.userId);
token = createToken.token;
await this.cacheAdapter.set(sessionId, token);
return { ok: true };
}
// check privateKey valid
const privateKey = await this.cacheAdapter.get(`${sessionId}_privateKey`);
if (!privateKey) {
return {
ok: false,
message: 'Invalid security arguments, please try again on your browser',
};
}
// check login name and password valid
const realPassword = decryptRSA(privateKey, password);
try {
ctx.tValidate(UserRule, {
name: username,
password: realPassword,
});
}
catch (err) {
const message = err.message;
return { ok: false, message: `Unauthorized, ${message}` };
}
const result = await this.userService.login(username, realPassword);
// user exists and password not match
if (result.code === LoginResultCode.Fail) {
return {
ok: false,
message: 'Please check your login name and password',
};
}
if (result.code === LoginResultCode.Success) {
// login success
// oxlint-disable-next-line typescript-eslint/no-non-null-assertion
token = result.token.token;
user = result.user;
// need unbind webauthn credential
if (needUnbindWan) {
await this.userService.removeWebauthnCredential(user?.userId, browserType);
}
}
else {
// others: LoginResultCode.UserNotFound
// create user request
const createRes = await this.userService.ensureTokenByUser({
name: username,
password: realPassword,
// FIXME: email verify
email: `${username}.cnpmjs.org`,
ip: ctx.ip,
});
token = createRes.token.token;
user = createRes.user;
}
await this.cacheAdapter.set(sessionId, token);
// webauthn registration
if (enableWebAuthn && isSupportWebAuthn && wanCredentialRegiData) {
try {
const verification = await verifyRegistrationResponse({
response: wanCredentialRegiData,
expectedChallenge,
expectedOrigin,
expectedRPID,
});
const { verified, registrationInfo } = verification;
if (verified && registrationInfo) {
const { credentialPublicKey, credentialID } = registrationInfo;
// @ts-expect-error type error
const base64CredentialPublicKey = base64url.encode(Buffer.from(new Uint8Array(credentialPublicKey)));
// @ts-expect-error type error
const base64CredentialID = base64url.encode(Buffer.from(new Uint8Array(credentialID)));
this.userService.createWebauthnCredential(user?.userId, {
credentialId: base64CredentialID,
publicKey: base64CredentialPublicKey,
browserType,
});
}
}
catch (err) {
this.logger.error('[WebauthController.loginImplement:verify-registration-fail] expectedChallenge: %s, expectedOrigin: %s, expectedRPID: %s, wanCredentialRegiData: %j, error: %j', expectedChallenge, expectedOrigin, expectedRPID, wanCredentialRegiData, err);
}
}
return { ok: true };
}
async loginPrepare(ctx, sessionId, name) {
ctx.tValidate(SessionRule, { sessionId });
const sessionToken = await this.cacheAdapter.get(sessionId);
if (typeof sessionToken !== 'string') {
return {
ok: false,
message: 'Session not found, please try again on your command line',
};
}
const browserType = getBrowserTypeForWebauthn(ctx.headers['user-agent']);
const expectedRPID = new URL(this.config.cnpmcore.registry).hostname;
const user = await this.userService.findUserByName(name);
const result = {
wanStatus: WanStatusCode.UserNotFound,
};
let credential;
if (user) {
credential = await this.userService.findWebauthnCredential(user.userId, browserType);
result.wanStatus = WanStatusCode.Unbound;
}
if (credential?.credentialId && credential?.publicKey) {
result.wanStatus = WanStatusCode.Bound;
result.wanCredentialAuthOption = generateAuthenticationOptions({
timeout: 60_000,
rpID: expectedRPID,
allowCredentials: [
{
// @ts-expect-error type error
id: base64url.toBuffer(credential.credentialId),
type: 'public-key',
transports: ['internal'],
},
],
});
await this.cacheAdapter.set(`${sessionId}_challenge`, result.wanCredentialAuthOption.challenge);
}
else {
const encoder = new TextEncoder();
const regUserIdBuffer = createHash('sha256').update(encoder.encode(name)).digest();
result.wanCredentialRegiOption = generateRegistrationOptions({
rpName: ctx.app.config.name,
rpID: expectedRPID,
// @ts-expect-error type error
userID: base64url.encode(Buffer.from(regUserIdBuffer)),
userName: name,
userDisplayName: name,
timeout: 60_000,
attestationType: 'direct',
authenticatorSelection: {
authenticatorAttachment: 'platform',
},
});
await this.cacheAdapter.set(`${sessionId}_challenge`, result.wanCredentialRegiOption.challenge);
}
return result;
}
async ssoRequest(ctx, sessionId) {
ctx.tValidate(SessionRule, { sessionId });
const sessionData = await this.cacheAdapter.get(sessionId);
if (sessionData !== '') {
throw new ForbiddenError('invalid sessionId');
}
// get current userInfo from infra
// @see https://github.com/eggjs/egg-userservice
const userRes = await this.authAdapter.ensureCurrentUser();
if (!userRes?.name || !userRes?.email) {
throw new ForbiddenError('invalid user info');
}
const { name, email } = userRes;
const { token } = await this.userService.ensureTokenByUser({
name,
email,
ip: ctx.ip,
});
await this.cacheAdapter.set(sessionId, token.token);
return { success: true };
}
async loginRequestSuccess(ctx) {
ctx.type = 'html';
return `<h1>😁😁😁 Authorization Successful 😁😁😁</h1>
<p>You can close this tab and return to your command line.</p>`;
}
async loginDone(ctx, sessionId) {
ctx.tValidate(SessionRule, { sessionId });
const token = await this.cacheAdapter.get(sessionId);
if (typeof token !== 'string') {
throw new NotFoundError('session not found');
}
if (token === '') {
ctx.status = 202;
ctx.set('retry-after', '1');
return { message: 'processing' };
}
// only get once
await this.cacheAdapter.delete(sessionId);
await this.cacheAdapter.delete(`${sessionId}_challenge`);
await this.cacheAdapter.delete(`${sessionId}_privateKey`);
return { token };
}
};
__decorate([
Inject(),
__metadata("design:type", Function)
], WebauthController.prototype, "cacheAdapter", void 0);
__decorate([
Inject(),
__metadata("design:type", Function)
], WebauthController.prototype, "authAdapter", void 0);
__decorate([
Inject(),
__metadata("design:type", Function)
], WebauthController.prototype, "logger", void 0);
__decorate([
Inject(),
__metadata("design:type", Object)
], WebauthController.prototype, "config", void 0);
__decorate([
Inject(),
__metadata("design:type", Function)
], WebauthController.prototype, "userService", void 0);
__decorate([
HTTPMethod({
path: '/-/v1/login',
method: HTTPMethodEnum.POST,
}),
__param(0, HTTPContext()),
__param(1, HTTPBody()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], WebauthController.prototype, "login", null);
__decorate([
HTTPMethod({
path: '/-/v1/login/request/session/:sessionId',
method: HTTPMethodEnum.GET,
}),
__param(0, HTTPContext()),
__param(1, HTTPParam()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String]),
__metadata("design:returntype", Promise)
], WebauthController.prototype, "loginRender", null);
__decorate([
HTTPMethod({
path: '/-/v1/login/request/session/:sessionId',
method: HTTPMethodEnum.POST,
}),
__param(0, HTTPContext()),
__param(1, HTTPParam()),
__param(2, HTTPBody()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String, Object]),
__metadata("design:returntype", Promise)
], WebauthController.prototype, "loginImplement", null);
__decorate([
HTTPMethod({
path: '/-/v1/login/request/prepare/:sessionId',
method: HTTPMethodEnum.GET,
}),
__param(0, HTTPContext()),
__param(1, HTTPParam()),
__param(2, HTTPQuery()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String, String]),
__metadata("design:returntype", Promise)
], WebauthController.prototype, "loginPrepare", null);
__decorate([
HTTPMethod({
path: '/-/v1/login/sso/:sessionId',
method: HTTPMethodEnum.POST,
}),
__param(0, HTTPContext()),
__param(1, HTTPParam()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String]),
__metadata("design:returntype", Promise)
], WebauthController.prototype, "ssoRequest", null);
__decorate([
HTTPMethod({
path: '/-/v1/login/request/success',
method: HTTPMethodEnum.GET,
}),
__param(0, HTTPContext()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Promise)
], WebauthController.prototype, "loginRequestSuccess", null);
__decorate([
HTTPMethod({
path: '/-/v1/login/done/session/:sessionId',
method: HTTPMethodEnum.GET,
}),
__param(0, HTTPContext()),
__param(1, HTTPParam()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, String]),
__metadata("design:returntype", Promise)
], WebauthController.prototype, "loginDone", null);
WebauthController = __decorate([
HTTPController()
], WebauthController);
export { WebauthController };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV2ViYXV0aENvbnRyb2xsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hcHAvcG9ydC93ZWJhdXRoL1dlYmF1dGhDb250cm9sbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUFBLE9BQU8sRUFBRSxVQUFVLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFFekMsT0FBTyx5QkFBeUIsQ0FBQztBQUNqQyxPQUFPLEVBQUUsSUFBSSxFQUFlLE1BQU0saUNBQWlDLENBQUM7QUFDcEUsT0FBTyxFQUdMLDZCQUE2QixFQUM3QiwyQkFBMkIsRUFDM0IsNEJBQTRCLEVBQzVCLDBCQUEwQixHQUMzQixNQUFNLHdCQUF3QixDQUFDO0FBS2hDLE9BQU8sU0FBUyxNQUFNLFdBQVcsQ0FBQztBQUNsQyxPQUFPLEVBRUwsV0FBVyxFQUNYLFFBQVEsRUFDUixjQUFjLEVBQ2QsVUFBVSxFQUNWLGNBQWMsRUFDZCxTQUFTLEVBQ1QsU0FBUyxFQUNULE1BQU0sR0FHUCxNQUFNLEtBQUssQ0FBQztBQUNiLE9BQU8sRUFBRSxjQUFjLEVBQUUsYUFBYSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRzNELE9BQU8sRUFBRSxVQUFVLEVBQUUsVUFBVSxFQUFFLE1BQU0sNEJBQTRCLENBQUM7QUFDcEUsT0FBTyxFQUFFLGVBQWUsRUFBRSxhQUFhLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUMzRSxPQUFPLEVBQUUseUJBQXlCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUdyRSxPQUFPLEVBQUUsb0JBQW9CLEVBQUUsTUFBTSx3QkFBd0IsQ0FBQztBQUU5RCxNQUFNLGdCQUFnQixHQUFHLElBQUksQ0FBQyxNQUFNLENBQUM7SUFDbkMsa0RBQWtEO0lBQ2xELFFBQVEsRUFBRSxJQUFJLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxTQUFTLEVBQUUsQ0FBQyxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsQ0FBQyxD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