cnpmcore
Version:
Private NPM Registry for Enterprise
215 lines • 18.2 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);
};
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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