cnpmcore
Version:
385 lines • 33 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); }
};
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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