UNPKG

cnpmcore

Version:
385 lines 33 kB
"use strict"; 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); } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.WebauthController = void 0; const tegg_1 = require("@eggjs/tegg"); const typebox_1 = require("@sinclair/typebox"); const egg_errors_1 = require("egg-errors"); const crypto_1 = require("crypto"); const base64url_1 = __importDefault(require("base64url")); const server_1 = require("@simplewebauthn/server"); const User_1 = require("../../common/enum/User"); const CacheAdapter_1 = require("../../common/adapter/CacheAdapter"); const UserService_1 = require("../../core/service/UserService"); const middleware_1 = require("../middleware"); const AuthAdapter_1 = require("../../infra/AuthAdapter"); const CryptoUtil_1 = require("../../common/CryptoUtil"); const UserUtil_1 = require("../../common/UserUtil"); const LoginRequestRule = typebox_1.Type.Object({ // cli 所在机器的 hostname hostname: typebox_1.Type.String({ minLength: 1, maxLength: 100 }), }); const UserRule = typebox_1.Type.Object({ name: typebox_1.Type.String({ minLength: 1, maxLength: 100 }), password: typebox_1.Type.String({ minLength: 8, maxLength: 100 }), }); const SessionRule = typebox_1.Type.Object({ // uuid sessionId: typebox_1.Type.String({ minLength: 36, maxLength: 36 }), }); let WebauthController = class WebauthController extends middleware_1.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 = (0, CryptoUtil_1.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) { if (!this.config.cnpmcore.admins[username]) { return { ok: false, message: 'Public registration is not allowed' }; } } const browserType = (0, UserUtil_1.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 (0, server_1.verifyAuthenticationResponse)({ response: wanCredentialAuthData, expectedChallenge, expectedOrigin, expectedRPID, authenticator: { credentialPublicKey: base64url_1.default.toBuffer(credential.publicKey), credentialID: base64url_1.default.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 = (0, CryptoUtil_1.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 === User_1.LoginResultCode.Fail) { return { ok: false, message: 'Please check your login name and password' }; } if (result.code === User_1.LoginResultCode.Success) { // login success 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 (0, server_1.verifyRegistrationResponse)({ response: wanCredentialRegiData, expectedChallenge, expectedOrigin, expectedRPID, }); const { verified, registrationInfo } = verification; if (verified && registrationInfo) { const { credentialPublicKey, credentialID } = registrationInfo; const base64CredentialPublicKey = base64url_1.default.encode(Buffer.from(new Uint8Array(credentialPublicKey))); const base64CredentialID = base64url_1.default.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 = (0, UserUtil_1.getBrowserTypeForWebauthn)(ctx.headers['user-agent']); const expectedRPID = new URL(this.config.cnpmcore.registry).hostname; const user = await this.userService.findUserByName(name); const result = { wanStatus: User_1.WanStatusCode.UserNotFound }; let credential; if (user) { credential = await this.userService.findWebauthnCredential(user.userId, browserType); result.wanStatus = User_1.WanStatusCode.Unbound; } if (credential?.credentialId && credential?.publicKey) { result.wanStatus = User_1.WanStatusCode.Bound; result.wanCredentialAuthOption = (0, server_1.generateAuthenticationOptions)({ timeout: 60000, rpID: expectedRPID, allowCredentials: [{ id: base64url_1.default.toBuffer(credential.credentialId), type: 'public-key', transports: ['internal'], }], }); await this.cacheAdapter.set(`${sessionId}_challenge`, result.wanCredentialAuthOption.challenge); } else { const encoder = new TextEncoder(); const regUserIdBuffer = (0, crypto_1.createHash)('sha256').update(encoder.encode(name)).digest(); result.wanCredentialRegiOption = (0, server_1.generateRegistrationOptions)({ rpName: ctx.app.config.name, rpID: expectedRPID, userID: base64url_1.default.encode(Buffer.from(regUserIdBuffer)), userName: name, userDisplayName: name, timeout: 60000, 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 egg_errors_1.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 egg_errors_1.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 egg_errors_1.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 }; } }; exports.WebauthController = WebauthController; __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", CacheAdapter_1.CacheAdapter) ], WebauthController.prototype, "cacheAdapter", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", AuthAdapter_1.AuthAdapter) ], WebauthController.prototype, "authAdapter", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", Object) ], WebauthController.prototype, "logger", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", Object) ], WebauthController.prototype, "config", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", UserService_1.UserService) ], WebauthController.prototype, "userService", void 0); __decorate([ (0, tegg_1.HTTPMethod)({ path: '/-/v1/login', method: tegg_1.HTTPMethodEnum.POST, }), __param(0, (0, tegg_1.Context)()), __param(1, (0, tegg_1.HTTPBody)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, Object]), __metadata("design:returntype", Promise) ], WebauthController.prototype, "login", null); __decorate([ (0, tegg_1.HTTPMethod)({ path: '/-/v1/login/request/session/:sessionId', method: tegg_1.HTTPMethodEnum.GET, }), __param(0, (0, tegg_1.Context)()), __param(1, (0, tegg_1.HTTPParam)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Promise) ], WebauthController.prototype, "loginRender", null); __decorate([ (0, tegg_1.HTTPMethod)({ path: '/-/v1/login/request/session/:sessionId', method: tegg_1.HTTPMethodEnum.POST, }), __param(0, (0, tegg_1.Context)()), __param(1, (0, tegg_1.HTTPParam)()), __param(2, (0, tegg_1.HTTPBody)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String, Object]), __metadata("design:returntype", Promise) ], WebauthController.prototype, "loginImplement", null); __decorate([ (0, tegg_1.HTTPMethod)({ path: '/-/v1/login/request/prepare/:sessionId', method: tegg_1.HTTPMethodEnum.GET, }), __param(0, (0, tegg_1.Context)()), __param(1, (0, tegg_1.HTTPParam)()), __param(2, (0, tegg_1.HTTPQuery)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String, String]), __metadata("design:returntype", Promise) ], WebauthController.prototype, "loginPrepare", null); __decorate([ (0, tegg_1.HTTPMethod)({ path: '/-/v1/login/sso/:sessionId', method: tegg_1.HTTPMethodEnum.POST, }), __param(0, (0, tegg_1.Context)()), __param(1, (0, tegg_1.HTTPParam)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Promise) ], WebauthController.prototype, "ssoRequest", null); __decorate([ (0, tegg_1.HTTPMethod)({ path: '/-/v1/login/request/success', method: tegg_1.HTTPMethodEnum.GET, }), __param(0, (0, tegg_1.Context)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object]), __metadata("design:returntype", Promise) ], WebauthController.prototype, "loginRequestSuccess", null); __decorate([ (0, tegg_1.HTTPMethod)({ path: '/-/v1/login/done/session/:sessionId', method: tegg_1.HTTPMethodEnum.GET, }), __param(0, (0, tegg_1.Context)()), __param(1, (0, tegg_1.HTTPParam)()), __metadata("design:type", Function), __metadata("design:paramtypes", [Object, String]), __metadata("design:returntype", Promise) ], WebauthController.prototype, "loginDone", null); exports.WebauthController = WebauthController = __decorate([ (0, tegg_1.HTTPController)() ], WebauthController); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiV2ViYXV0aENvbnRyb2xsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hcHAvcG9ydC93ZWJhdXRoL1dlYmF1dGhDb250cm9sbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7Ozs7OztBQUFBLHNDQVVxQjtBQUtyQiwrQ0FBaUQ7QUFDakQsMkNBQTJEO0FBQzNELG1DQUFvQztBQUNwQywwREFBa0M7QUFDbEMsbURBT2dDO0FBRWhDLGlEQUF3RTtBQUN4RSxvRUFBaUU7QUFDakUsZ0VBQTZEO0FBQzdELDhDQUFxRDtBQUNyRCx5REFBc0Q7QUFDdEQsd0RBQWlFO0FBQ2pFLG9EQUFrRTtBQUVsRSxNQUFNLGdCQUFnQixHQUFHLGNBQUksQ0FBQyxNQUFNLENBQUM7SUFDbkMscUJBQXFCO0lBQ3JCLFFBQVEsRUFBRSxjQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsU0FBUyxFQUFFLENBQUMsRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFLENBQUM7Q0FDeEQsQ0FBQyxDQUFDO0FBb0JILE1BQU0sUUFBUSxHQUFHLGNBQUksQ0FBQyxNQUFNLENBQUM7SUFDM0IsSUFBSSxFQUFFLGNBQUksQ0FBQyxNQUFNLENBQUMsRUFBRSxTQUFTLEVBQUUsQ0FBQyxFQUFFLFNBQVMsRUFBRSxHQUFHLEVBQUUsQ0FBQztJQUNuRCxRQUFRLEVBQUUsY0FBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLFNBQVMsRUFBRSxDQUFDLEVBQUUsU0FBUyxFQUFFLEdBQUcsRUFBRSxDQUFDO0NBQ3hELENBQUMsQ0FBQztBQUVILE1BQU0sV0FBVyxHQUFHLGNBQUksQ0FBQyxNQUFNLENBQUM7SUFDOUIsT0FBTztJQUNQLFNBQVMsRUFBRSxjQUFJLENBQUMsTUFBTSxDQUFDLEVBQUUsU0FBUyxFQUFFLEVBQUUsRUFBRSxTQUFTLEVBQUUsRUFBRSxFQUFFLENBQUM7Q0FDekQsQ0FBQyxDQUFDO0FBR0ksSUFBTSxpQkFBaUIsR0FBdkIsTUFBTSxp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