UNPKG

cnpmcore

Version:

Private NPM Registry for Enterprise

265 lines 18.9 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 { 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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