UNPKG

cnpmcore

Version:

Private NPM Registry for Enterprise

414 lines 33.8 kB
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}@webauth.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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