UNPKG

cnpmcore

Version:
188 lines 16.5 kB
"use strict"; 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 __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.UserService = void 0; const crypto_1 = __importDefault(require("crypto")); const tegg_1 = require("@eggjs/tegg"); const egg_errors_1 = require("egg-errors"); const UserRepository_1 = require("../../repository/UserRepository"); const User_1 = require("../entity/User"); const Token_1 = require("../entity/Token"); const WebauthnCredential_1 = require("../entity/WebauthnCredential"); const User_2 = require("../../common/enum/User"); const UserUtil_1 = require("../../common/UserUtil"); const AbstractService_1 = require("../../common/AbstractService"); const RegistryManagerService_1 = require("./RegistryManagerService"); const PackageUtil_1 = require("../../common/PackageUtil"); let UserService = class UserService extends AbstractService_1.AbstractService { checkPassword(user, password) { const plain = `${user.passwordSalt}${password}`; return (0, UserUtil_1.checkIntegrity)(plain, user.passwordIntegrity); } async findUserByNameOrDisplayName(name) { const hasPrefix = name.includes(':'); if (hasPrefix) { return await this.findUserByName(name); } const selfRegistry = await this.registryManagerService.ensureSelfRegistry(); const selfUser = await this.findUserByName((0, PackageUtil_1.getPrefixedName)(selfRegistry.userPrefix, name)); if (selfUser) { return selfUser; } const defaultRegistry = await this.registryManagerService.ensureDefaultRegistry(); const defaultUser = await this.findUserByName((0, PackageUtil_1.getPrefixedName)(defaultRegistry.userPrefix, name)); return defaultUser; } async findInRegistry(registry, name) { return await this.findUserByName((0, PackageUtil_1.getPrefixedName)(registry.userPrefix, name)); } async findUserByName(name) { return await this.userRepository.findUserByName(name); } async login(name, password) { const user = await this.userRepository.findUserByName(name); if (!user) return { code: User_2.LoginResultCode.UserNotFound }; if (!this.checkPassword(user, password)) { return { code: User_2.LoginResultCode.Fail }; } const token = await this.createToken(user.userId); return { code: User_2.LoginResultCode.Success, user, token }; } async findOrCreateUser({ name, email, ip, password = crypto_1.default.randomUUID() }) { let user = await this.userRepository.findUserByName(name); if (!user) { const createRes = await this.create({ name, email, password, ip, }); user = createRes.user; } return user; } async ensureTokenByUser(opts) { const user = await this.findOrCreateUser(opts); const token = await this.createToken(user.userId); return { user, token }; } async create(createUser) { const passwordSalt = crypto_1.default.randomBytes(30).toString('hex'); const plain = `${passwordSalt}${createUser.password}`; const passwordIntegrity = (0, UserUtil_1.integrity)(plain); const userEntity = User_1.User.create({ name: createUser.name, email: createUser.email, ip: createUser.ip, passwordSalt, passwordIntegrity, isPrivate: true, }); await this.userRepository.saveUser(userEntity); const token = await this.createToken(userEntity.userId); return { user: userEntity, token }; } async saveUser(userPrefix = 'npm:', name, email) { const storeName = name.startsWith('name:') ? name : `${userPrefix}${name}`; let user = await this.userRepository.findUserByName(storeName); if (!user) { const passwordSalt = crypto_1.default.randomBytes(20).toString('hex'); const passwordIntegrity = (0, UserUtil_1.integrity)(passwordSalt); user = User_1.User.create({ name: storeName, email, ip: '', passwordSalt, passwordIntegrity, isPrivate: false, }); await this.userRepository.saveUser(user); return { changed: true, user }; } if (user.email === email) { // skip return { changed: false, user }; } user.email = email; await this.userRepository.saveUser(user); return { changed: true, user }; } async createToken(userId, options = {}) { // https://github.blog/2021-09-23-announcing-npms-new-access-token-format/ // https://github.blog/2021-04-05-behind-githubs-new-authentication-token-formats/ // https://github.blog/changelog/2022-12-06-limit-scope-of-npm-tokens-with-the-new-granular-access-tokens/ const token = (0, UserUtil_1.randomToken)(this.config.cnpmcore.name); const tokenKey = (0, UserUtil_1.sha512)(token); const tokenMark = token.substring(0, token.indexOf('_') + 4); const tokenEntity = Token_1.Token.create({ tokenKey, tokenMark, userId, ...options, }); await this.userRepository.saveToken(tokenEntity); tokenEntity.token = token; return tokenEntity; } async removeToken(userId, tokenKeyOrTokenValue) { let token = await this.userRepository.findTokenByTokenKey(tokenKeyOrTokenValue); if (!token) { // tokenKeyOrTokenValue is token value, sha512 and find again token = await this.userRepository.findTokenByTokenKey((0, UserUtil_1.sha512)(tokenKeyOrTokenValue)); } if (!token) { throw new egg_errors_1.NotFoundError(`Token "${tokenKeyOrTokenValue}" not exists`); } if (token.userId !== userId) { throw new egg_errors_1.ForbiddenError(`Not authorized to remove token "${tokenKeyOrTokenValue}"`); } await this.userRepository.removeToken(token.tokenId); } async findWebauthnCredential(userId, browserType) { const credential = await this.userRepository.findCredentialByUserIdAndBrowserType(userId, browserType || null); return credential; } async createWebauthnCredential(userId, options) { const credentialEntity = WebauthnCredential_1.WebauthnCredential.create({ userId: userId, credentialId: options.credentialId, publicKey: options.publicKey, browserType: options.browserType, }); await this.userRepository.saveCredential(credentialEntity); return credentialEntity; } async removeWebauthnCredential(userId, browserType) { const credential = await this.userRepository.findCredentialByUserIdAndBrowserType(userId, browserType || null); if (credential) { await this.userRepository.removeCredential(credential.wancId); } } }; exports.UserService = UserService; __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", UserRepository_1.UserRepository) ], UserService.prototype, "userRepository", void 0); __decorate([ (0, tegg_1.Inject)(), __metadata("design:type", RegistryManagerService_1.RegistryManagerService) ], UserService.prototype, "registryManagerService", void 0); exports.UserService = UserService = __decorate([ (0, tegg_1.SingletonProto)({ accessLevel: tegg_1.AccessLevel.PUBLIC, }) ], UserService); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXNlclNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hcHAvY29yZS9zZXJ2aWNlL1VzZXJTZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7Ozs7OztBQUFBLG9EQUE0QjtBQUM1QixzQ0FJcUI7QUFDckIsMkNBQTJEO0FBQzNELG9FQUFpRTtBQUNqRSx5Q0FBb0Q7QUFDcEQsMkNBQWtFO0FBQ2xFLHFFQUE4RjtBQUM5RixpREFBeUQ7QUFDekQsb0RBQXVGO0FBQ3ZGLGtFQUErRDtBQUMvRCxxRUFBa0U7QUFDbEUsMERBQTJEO0FBOENwRCxJQUFNLFdBQVcsR0FBakIsTUFBTSxXQUFZLFNBQVEsaUNBQWU7SUFNOUMsYUFBYSxDQUFDLElBQWdCLEVBQUUsUUFBZ0I7UUFDOUMsTUFBTSxLQUFLLEdBQUcsR0FBRyxJQUFJLENBQUMsWUFBWSxHQUFHLFFBQVEsRUFBRSxDQUFDO1FBQ2hELE9BQU8sSUFBQSx5QkFBYyxFQUFDLEtBQUssRUFBRSxJQUFJLENBQUMsaUJBQWlCLENBQUMsQ0FBQztJQUN2RCxDQUFDO0lBRUQsS0FBSyxDQUFDLDJCQUEyQixDQUFDLElBQVk7UUFDNUMsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUNyQyxJQUFJLFNBQVMsRUFBRTtZQUNiLE9BQU8sTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDO1NBQ3hDO1FBRUQsTUFBTSxZQUFZLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUM1RSxNQUFNLFFBQVEsR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsSUFBQSw2QkFBZSxFQUFDLFlBQVksQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUMzRixJQUFJLFFBQVEsRUFBRTtZQUNaLE9BQU8sUUFBUSxDQUFDO1NBQ2pCO1FBRUQs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