cnpmcore
Version:
Private NPM Registry for Enterprise
265 lines • 18.9 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 { Type } from '@eggjs/typebox-validate/typebox';
import { HTTPContext, Context, HTTPBody, HTTPController, HTTPMethod, HTTPMethodEnum, HTTPParam } from 'egg';
import { ForbiddenError, NotFoundError, UnauthorizedError, UnprocessableEntityError } from 'egg/errors';
import { LoginResultCode } from "../../common/enum/User.js";
import { sha512 } from "../../common/UserUtil.js";
import { isGranularToken } from "../../core/entity/Token.js";
import { AbstractController } from "./AbstractController.js";
// body: {
// _id: 'org.couchdb.user:dddd',
// name: 'dddd',
// password: '***',
// type: 'user',
// roles: [],
// date: '2021-12-03T13:14:21.712Z'
// }
// create user will contains email
// {
// _id: 'org.couchdb.user:awldj',
// name: 'awldj',
// password: '***',
// email: 'ddd@dawd.com',
// type: 'user',
// roles: [],
// date: '2021-12-03T13:46:30.644Z'
// }
const UserRule = Type.Object({
type: Type.Literal('user'),
// date: Type.String({ format: 'date-time' }),
name: Type.String({ minLength: 1, maxLength: 100 }),
// https://docs.npmjs.com/policies/security#password-policies
// Passwords should contain alpha-numeric characters and symbols.
// Passwords should be a minimum of 8 characters.
password: Type.String({ minLength: 8, maxLength: 100 }),
email: Type.Optional(Type.String({ format: 'email' })),
});
let UserController = class UserController extends AbstractController {
// https://github.com/npm/npm-profile/blob/main/lib/index.js#L127
async loginOrCreateUser(ctx, username, user) {
// headers: {
// 'user-agent': 'npm/8.1.2 node/v16.13.1 darwin arm64 workspaces/false',
// 'npm-command': 'adduser',
// 'content-type': 'application/json',
// accept: '*/*',
// 'content-length': '124',
// 'accept-encoding': 'gzip,deflate',
// host: 'localhost:7001',
// connection: 'keep-alive'
// }
// console.log(username, user, ctx.headers, ctx.href);
ctx.tValidate(UserRule, user);
if (username !== user.name) {
throw new UnprocessableEntityError(`username(${username}) not match user.name(${user.name})`);
}
if (this.config.cnpmcore.allowPublicRegistration === false && !this.config.cnpmcore.admins[user.name]) {
throw new ForbiddenError('Public registration is not allowed');
}
const result = await this.userService.login(user.name, user.password);
// user exists and password not match
if (result.code === LoginResultCode.Fail) {
throw new UnauthorizedError('Please check your login name and password');
}
if (result.code === LoginResultCode.Success) {
// login success
// TODO: 2FA feature
ctx.status = 201;
return {
ok: true,
id: `org.couchdb.user:${result.user?.displayName}`,
rev: result.user?.userId,
token: result.token?.token,
};
}
// others: LoginResultCode.UserNotFound
// 1. login request
if (!user.email) {
// user not exists
throw new NotFoundError(`User ${user.name} not exists`);
}
// 2. create user request
const { user: userEntity, token } = await this.userService.create({
name: user.name,
password: user.password,
email: user.email,
ip: ctx.ip,
});
ctx.status = 201;
return {
ok: true,
id: `org.couchdb.user:${userEntity.displayName}`,
rev: userEntity.userId,
token: token.token,
};
}
// https://github.com/npm/cli/blob/latest/lib/commands/logout.js#L24
async logout(ctx, token) {
const authorizedUserAndToken = await this.userRoleManager.getAuthorizedUserAndToken(ctx);
if (!authorizedUserAndToken)
return { ok: false };
if (authorizedUserAndToken.token.tokenKey !== sha512(token)) {
throw new UnprocessableEntityError('invalid token');
}
await this.userService.removeToken(authorizedUserAndToken.user.userId, token);
return { ok: true };
}
// https://github.com/npm/cli/blob/latest/lib/commands/owner.js#L154
async showUser(ctx, username) {
const user = await this.userService.findUserByNameOrDisplayName(username);
if (!user) {
throw new NotFoundError(`User "${username}" not found`);
}
const authorized = await this.userRoleManager.getAuthorizedUserAndToken(ctx);
return {
_id: `org.couchdb.user:${user.displayName}`,
name: user.displayName,
email: authorized ? user.email : undefined,
};
}
// https://github.com/npm/cli/blob/latest/lib/utils/get-identity.js#L20
async whoami(ctx) {
await this.userRoleManager.requiredAuthorizedUser(ctx, 'read');
const authorizedRes = await this.userRoleManager.getAuthorizedUserAndToken(ctx);
// oxlint-disable-next-line typescript/no-non-null-assertion
const { token, user } = authorizedRes;
if (isGranularToken(token)) {
const { name, description, expiredAt, allowedPackages, allowedScopes, lastUsedAt, type } = token;
return {
username: user.displayName,
name,
description,
allowedPackages,
allowedScopes,
lastUsedAt,
expiredAt,
// do not return token value
// token: token.token,
key: token.tokenKey,
cidr_whitelist: token.cidrWhitelist,
readonly: token.isReadonly,
created: token.createdAt,
updated: token.updatedAt,
type,
};
}
return {
username: user.displayName,
};
}
// https://github.com/cnpm/cnpmcore/issues/64
async starredByUser() {
throw new ForbiddenError('npm stars is not allowed');
}
// https://github.com/cnpm/cnpmcore/issues/64
async showProfile(ctx) {
const authorizedUser = await this.userRoleManager.requiredAuthorizedUser(ctx, 'read');
return {
// "tfa": {
// "pending": false,
// "mode": "auth-only"
// },
name: authorizedUser.displayName,
email: authorizedUser.email,
email_verified: false,
created: authorizedUser.createdAt,
updated: authorizedUser.updatedAt,
// fullname: authorizedUser.name,
// twitter: '',
// github: '',
};
}
// https://github.com/cnpm/cnpmcore/issues/64
async saveProfile() {
// Valid properties are: email, password, fullname, homepage, freenode, twitter, github
// { email: 'admin@cnpmjs.org', homepage: 'fengmk2' }
throw new ForbiddenError('npm profile set is not allowed');
}
};
__decorate([
HTTPMethod({
path: '/-/user/org.couchdb.user::username',
method: HTTPMethodEnum.PUT,
}),
__param(0, HTTPContext()),
__param(1, HTTPParam()),
__param(2, HTTPBody()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Context, String, Object]),
__metadata("design:returntype", Promise)
], UserController.prototype, "loginOrCreateUser", null);
__decorate([
HTTPMethod({
path: '/-/user/token/:token',
method: HTTPMethodEnum.DELETE,
}),
__param(0, HTTPContext()),
__param(1, HTTPParam()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Context, String]),
__metadata("design:returntype", Promise)
], UserController.prototype, "logout", null);
__decorate([
HTTPMethod({
path: '/-/user/org.couchdb.user::username',
method: HTTPMethodEnum.GET,
}),
__param(0, HTTPContext()),
__param(1, HTTPParam()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Context, String]),
__metadata("design:returntype", Promise)
], UserController.prototype, "showUser", null);
__decorate([
HTTPMethod({
path: '/-/whoami',
method: HTTPMethodEnum.GET,
}),
__param(0, HTTPContext()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Context]),
__metadata("design:returntype", Promise)
], UserController.prototype, "whoami", null);
__decorate([
HTTPMethod({
path: '/-/_view/starredByUser',
method: HTTPMethodEnum.GET,
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], UserController.prototype, "starredByUser", null);
__decorate([
HTTPMethod({
path: '/-/npm/v1/user',
method: HTTPMethodEnum.GET,
}),
__param(0, HTTPContext()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Context]),
__metadata("design:returntype", Promise)
], UserController.prototype, "showProfile", null);
__decorate([
HTTPMethod({
path: '/-/npm/v1/user',
method: HTTPMethodEnum.POST,
}),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Promise)
], UserController.prototype, "saveProfile", null);
UserController = __decorate([
HTTPController()
], UserController);
export { UserController };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXNlckNvbnRyb2xsZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hcHAvcG9ydC9jb250cm9sbGVyL1VzZXJDb250cm9sbGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7OztBQUFBLE9BQU8sRUFBRSxJQUFJLEVBQWUsTUFBTSxpQ0FBaUMsQ0FBQztBQUNwRSxPQUFPLEVBQUUsV0FBVyxFQUFFLE9BQU8sRUFBRSxRQUFRLEVBQUUsY0FBYyxFQUFFLFVBQVUsRUFBRSxjQUFjLEVBQUUsU0FBUyxFQUFFLE1BQU0sS0FBSyxDQUFDO0FBQzVHLE9BQU8sRUFBRSxjQUFjLEVBQUUsYUFBYSxFQUFFLGlCQUFpQixFQUFFLHdCQUF3QixFQUFFLE1BQU0sWUFBWSxDQUFDO0FBRXhHLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFDbEQsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQzdELE9BQU8sRUFBRSxrQkFBa0IsRUFBRSxNQUFNLHlCQUF5QixDQUFDO0FBRTdELFVBQVU7QUFDVixrQ0FBa0M7QUFDbEMsa0JBQWtCO0FBQ2xCLHFCQUFxQjtBQUNyQixrQkFBa0I7QUFDbEIsZUFBZTtBQUNmLHFDQUFxQztBQUNyQyxJQUFJO0FBQ0osa0NBQWtDO0FBQ2xDLElBQUk7QUFDSixtQ0FBbUM7QUFDbkMsbUJBQW1CO0FBQ25CLHFCQUFxQjtBQUNyQiwyQkFBMkI7QUFDM0Isa0JBQWtCO0FBQ2xCLGVBQWU7QUFDZixxQ0FBcUM7QUFDckMsSUFBSTtBQUNKLE1BQU0sUUFBUSxHQUFHLElBQUksQ0FBQyxNQUFNLENBQUM7SUFDM0IsSUFBSSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDO0lBQzFCLDhDQUE4QztJQUM5QyxJQUFJLEVBQUUsSUFBSSxDQUFDLE1BQU0sQ0FBQyxF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