UNPKG

cnpmcore

Version:

Private NPM Registry for Enterprise

215 lines 18.2 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); }; import { AccessLevel, ContextProto, Inject } from 'egg'; import { ForbiddenError, UnauthorizedError } from 'egg/errors'; import { getScopeAndName } from "../common/PackageUtil.js"; let UserRoleManager = class UserRoleManager { constructor() { this.handleAuthorized = false; } // check publish access // 1. admin has all access // 2. has published in current registry // 3. pkg scope is allowed to publish // use AbstractController#ensurePublishAccess ensure pkg exists; async checkPublishAccess(ctx, fullname) { const user = await this.requiredAuthorizedUser(ctx, 'publish'); // 1. admin has all access const isAdmin = await this.isAdmin(ctx); if (isAdmin) { return user; } // 2. check for checkGranularTokenAccess const authorizedUserAndToken = await this.getAuthorizedUserAndToken(ctx); // oxlint-disable-next-line typescript-eslint/no-non-null-assertion const { token } = authorizedUserAndToken; await this.tokenService.checkGranularTokenAccess(token, fullname); // 3. has published in current registry const [scope, name] = getScopeAndName(fullname); const pkg = await this.packageRepository.findPackage(scope, name); const selfRegistry = await this.registryManagerService.ensureSelfRegistry(); const inSelfRegistry = pkg?.registryId === selfRegistry.registryId; if (inSelfRegistry) { // 3.1 check in Maintainers table // Higher priority than scope check await this.requiredPackageMaintainer(pkg, user); return user; } if (pkg && !scope && !inSelfRegistry) { // 3.2 public package can't publish in other registry // scope package can be migrated into self registry throw new ForbiddenError(`Can't modify npm public package "${fullname}"`); } // 4 check scope is allowed to publish await this.requiredPackageScope(scope, user); if (pkg) { // published scoped package await this.requiredPackageMaintainer(pkg, user); } return user; } // { // 'user-agent': 'npm/8.1.2 node/v16.13.1 darwin arm64 workspaces/false', // 'npm-command': 'adduser', // authorization: 'Bearer 379f84d8-ba98-480b-909e-a8260af3a3ee', // 'content-type': 'application/json', // accept: '*/*', // 'content-length': '166', // 'accept-encoding': 'gzip,deflate', // host: 'localhost:7001', // connection: 'keep-alive' // } async getAuthorizedUserAndToken(ctx) { if (this.handleAuthorized) { if (!this.currentAuthorizedUser) return null; return { token: this.currentAuthorizedToken, user: this.currentAuthorizedUser, }; } this.handleAuthorized = true; const authorization = ctx.get('authorization'); if (!authorization) return null; const authorizedUserAndToken = await this.tokenService.getUserAndToken(authorization); if (!authorizedUserAndToken) { return null; } // check token expired & set lastUsedAt await this.tokenService.checkTokenStatus(authorizedUserAndToken.token); this.currentAuthorizedToken = authorizedUserAndToken.token; this.currentAuthorizedUser = authorizedUserAndToken.user; ctx.userId = authorizedUserAndToken.user.userId; return authorizedUserAndToken; } async requiredAuthorizedUser(ctx, role) { const authorizedUserAndToken = await this.getAuthorizedUserAndToken(ctx); if (!authorizedUserAndToken) { const authorization = ctx.get('authorization'); const message = authorization ? 'Invalid token' : 'Login first'; throw new UnauthorizedError(message); } const { user, token } = authorizedUserAndToken; // only enable npm client and version check setting will go into this condition if (this.config.cnpmcore.enableNpmClientAndVersionCheck && role === 'publish') { if (token.isReadonly) { throw new ForbiddenError(`Read-only Token "${token.tokenMark}" can't publish`); } const userAgent = ctx.get('user-agent'); // only support npm >= 7.0.0 allow publish action // user-agent: "npm/6.14.12 node/v10.24.1 darwin x64" // pnpm: "pnpm/10.17.0 npm/? node/v20.19.5 darwin arm64" const isPnpm = userAgent.startsWith('pnpm/'); const m = /\bnpm\/(\d{1,5})\./.exec(userAgent); if (m) { const major = Number.parseInt(m[1]); if (major < 7) { throw new ForbiddenError('Only allow npm@>=7.0.0 client to access'); } } else if (!isPnpm) { throw new ForbiddenError('Only allow npm client to access'); } } if (role === 'setting') { if (token.isReadonly) { throw new ForbiddenError(`Read-only Token "${token.tokenMark}" can't setting`); } if (token.isAutomation) { throw new ForbiddenError(`Automation Token "${token.tokenMark}" can't setting`); } } return user; } async requiredPackageMaintainer(pkg, user) { const maintainers = await this.packageRepository.listPackageMaintainers(pkg.packageId); const maintainer = maintainers.find((m) => m.userId === user.userId); if (!maintainer) { const names = maintainers.map((m) => m.name).join(', '); throw new ForbiddenError(`"${user.name}" not authorized to modify ${pkg.fullname}, please contact maintainers: "${names}"`); } } async requiredPackageScope(scope, user) { const cnpmcoreConfig = this.config.cnpmcore; if (cnpmcoreConfig.allowPublishNonScopePackage) { return; } const allowScopes = user.scopes ?? cnpmcoreConfig.allowScopes; if (!scope) { throw new ForbiddenError(`Package scope required, legal scopes: "${allowScopes.join(', ')}"`); } if (!allowScopes.includes(scope)) { throw new ForbiddenError(`Scope "${scope}" not match legal scopes: "${allowScopes.join(', ')}"`); } } async isAdmin(ctx) { const authorizedUserAndToken = await this.getAuthorizedUserAndToken(ctx); if (!authorizedUserAndToken) return false; const { user, token } = authorizedUserAndToken; if (token.isReadonly) return false; return user.name in this.config.cnpmcore.admins; } // self scope + no team binding = everyone can read, returns false // self scope + team binding = only team members can read, returns true // returns true if package is team-bound (private cache needed) async checkReadAccess(ctx, scope, name) { if (!scope || !this.config.cnpmcore.allowScopes.includes(scope)) return false; const pkg = await this.packageRepository.findPackage(scope, name); if (!pkg) return false; // let downstream throw 404 const hasTeamBinding = await this.teamRepository.hasAnyTeamBinding(pkg.packageId); if (!hasTeamBinding) return false; // no team binding, everyone can read // team binding exists, require auth const user = await this.requiredAuthorizedUser(ctx, 'read'); if (await this.isAdmin(ctx)) return true; const hasAccess = await this.teamRepository.hasPackageAccess(pkg.packageId, user.userId); if (hasAccess) return true; throw new ForbiddenError(`"${user.name}" is not authorized to access ${pkg.fullname}`); } }; __decorate([ Inject(), __metadata("design:type", Function) ], UserRoleManager.prototype, "packageRepository", void 0); __decorate([ Inject(), __metadata("design:type", Object) ], UserRoleManager.prototype, "config", void 0); __decorate([ Inject(), __metadata("design:type", Function) ], UserRoleManager.prototype, "logger", void 0); __decorate([ Inject(), __metadata("design:type", Function) ], UserRoleManager.prototype, "registryManagerService", void 0); __decorate([ Inject(), __metadata("design:type", Function) ], UserRoleManager.prototype, "tokenService", void 0); __decorate([ Inject(), __metadata("design:type", Function) ], UserRoleManager.prototype, "teamRepository", void 0); UserRoleManager = __decorate([ ContextProto({ // only inject on port module accessLevel: AccessLevel.PRIVATE, }) ], UserRoleManager); export { UserRoleManager }; //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXNlclJvbGVNYW5hZ2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vYXBwL3BvcnQvVXNlclJvbGVNYW5hZ2VyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBLE9BQU8sRUFBVyxXQUFXLEVBQUUsWUFBWSxFQUFFLE1BQU0sRUFBa0IsTUFBTSxLQUFLLENBQUM7QUFDakYsT0FBTyxFQUFFLGNBQWMsRUFBRSxpQkFBaUIsRUFBRSxNQUFNLFlBQVksQ0FBQztBQUUvRCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sMEJBQTBCLENBQUM7QUFnQnBELElBQU0sZUFBZSxHQUFyQixNQUFNLGVBQWU7SUFBckI7UUFjRyxxQkFBZ0IsR0FBRyxLQUFLLENBQUM7SUFvTG5DLENBQUM7SUFoTEMsdUJBQXVCO0lBQ3ZCLDBCQUEwQjtJQUMxQix1Q0FBdUM7SUFDdkMscUNBQXFDO0lBQ3JDLGdFQUFnRTtJQUN6RCxLQUFLLENBQUMsa0JBQWtCLENBQUMsR0FBWSxFQUFFLFFBQWdCO1FBQzVELE1BQU0sSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLHNCQUFzQixDQUFDLEdBQUcsRUFBRSxTQUFTLENBQUMsQ0FBQztRQUUvRCwwQkFBMEI7UUFDMUIsTUFBTSxPQUFPLEdBQUcsTUFBTSxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ3hDLElBQUksT0FBTyxFQUFFLENBQUM7WUFDWixPQUFPLElBQUksQ0FBQztRQUNkLENBQUM7UUFFRCx3Q0FBd0M7UUFDeEMsTUFBTSxzQkFBc0IsR0FBRyxNQUFNLElBQUksQ0FBQyx5QkFBeUIsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUN6RSxtRUFBbUU7UUFDbkUsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLHNCQUF1QixDQUFDO1FBQzFDLE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyx3QkFBd0IsQ0FBQyxLQUFLLEVBQUUsUUFBUSxDQUFDLENBQUM7UUFFbEUsdUNBQXVD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