cnpmcore
Version:
Private NPM Registry for Enterprise
180 lines • 16.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 crypto from 'node:crypto';
import { AccessLevel, Inject, SingletonProto } from 'egg';
import { ForbiddenError, NotFoundError } from 'egg/errors';
import { AbstractService } from "../../common/AbstractService.js";
import { LoginResultCode } from "../../common/enum/User.js";
import { getPrefixedName } from "../../common/PackageUtil.js";
import { checkIntegrity, integrity, randomToken, sha512 } from "../../common/UserUtil.js";
import { Token as TokenEntity } from "../entity/Token.js";
import { User as UserEntity } from "../entity/User.js";
import { WebauthnCredential as WebauthnCredentialEntity } from "../entity/WebauthnCredential.js";
let UserService = class UserService extends AbstractService {
checkPassword(user, password) {
const plain = `${user.passwordSalt}${password}`;
return 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(getPrefixedName(selfRegistry.userPrefix, name));
if (selfUser) {
return selfUser;
}
const defaultRegistry = await this.registryManagerService.ensureDefaultRegistry();
const defaultUser = await this.findUserByName(getPrefixedName(defaultRegistry.userPrefix, name));
return defaultUser;
}
async findInRegistry(registry, name) {
return await this.findUserByName(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: LoginResultCode.UserNotFound };
if (!this.checkPassword(user, password)) {
return { code: LoginResultCode.Fail };
}
const token = await this.createToken(user.userId);
return { code: LoginResultCode.Success, user, token };
}
async findOrCreateUser({ name, email, ip, password = crypto.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.randomBytes(30).toString('hex');
const plain = `${passwordSalt}${createUser.password}`;
const passwordIntegrity = integrity(plain);
const userEntity = UserEntity.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(name, email, userPrefix = 'npm:') {
const storeName = name.startsWith('name:') ? name : `${userPrefix}${name}`;
let user = await this.userRepository.findUserByName(storeName);
if (!user) {
const passwordSalt = crypto.randomBytes(20).toString('hex');
const passwordIntegrity = integrity(passwordSalt);
user = UserEntity.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 = randomToken(this.config.cnpmcore.name);
const tokenKey = sha512(token);
const tokenMark = token.slice(0, token.indexOf('_') + 4);
const tokenEntity = TokenEntity.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(sha512(tokenKeyOrTokenValue));
}
if (!token) {
throw new NotFoundError(`Token "${tokenKeyOrTokenValue}" not exists`);
}
if (token.userId !== userId) {
throw new 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 = WebauthnCredentialEntity.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);
}
}
};
__decorate([
Inject(),
__metadata("design:type", Function)
], UserService.prototype, "userRepository", void 0);
__decorate([
Inject(),
__metadata("design:type", Function)
], UserService.prototype, "registryManagerService", void 0);
UserService = __decorate([
SingletonProto({
accessLevel: AccessLevel.PUBLIC,
})
], UserService);
export { UserService };
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiVXNlclNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9hcHAvY29yZS9zZXJ2aWNlL1VzZXJTZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7OztBQUFBLE9BQU8sTUFBTSxNQUFNLGFBQWEsQ0FBQztBQUVqQyxPQUFPLEVBQUUsV0FBVyxFQUFFLE1BQU0sRUFBRSxjQUFjLEVBQUUsTUFBTSxLQUFLLENBQUM7QUFDMUQsT0FBTyxFQUFFLGNBQWMsRUFBRSxhQUFhLEVBQUUsTUFBTSxZQUFZLENBQUM7QUFFM0QsT0FBTyxFQUFFLGVBQWUsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQ2xFLE9BQU8sRUFBRSxlQUFlLEVBQUUsTUFBTSwyQkFBMkIsQ0FBQztBQUM1RCxPQUFPLEVBQUUsZUFBZSxFQUFFLE1BQU0sNkJBQTZCLENBQUM7QUFDOUQsT0FBTyxFQUFFLGNBQWMsRUFBRSxTQUFTLEVBQUUsV0FBVyxFQUFFLE1BQU0sRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBRzFGLE9BQU8sRUFBRSxLQUFLLElBQUksV0FBVyxFQUFrQixNQUFNLG9CQUFvQixDQUFDO0FBQzFFLE9BQU8sRUFBRSxJQUFJLElBQUksVUFBVSxFQUFFLE1BQU0sbUJBQW1CLENBQUM7QUFDdkQsT0FBTyxFQUFFLGtCQUFrQixJQUFJLHdCQUF3QixFQUFFLE1BQU0saUNBQWlDLENBQUM7QUE4QzFGLElBQU0sV0FBVyxHQUFqQixNQUFNLFdBQVksU0FBUSxlQUFlO0lBTTlDLGFBQWEsQ0FBQyxJQUFnQixFQUFFLFFBQWdCO1FBQzlDLE1BQU0sS0FBSyxHQUFHLEdBQUcsSUFBSSxDQUFDLFlBQVksR0FBRyxRQUFRLEVBQUUsQ0FBQztRQUNoRCxPQUFPLGNBQWMsQ0FBQyxLQUFLLEVBQUUsSUFBSSxDQUFDLGlCQUFpQixDQUFDLENBQUM7SUFDdkQsQ0FBQztJQUVELEtBQUssQ0FBQywyQkFBMkIsQ0FBQyxJQUFZO1FBQzVDLE1BQU0sU0FBUyxHQUFHLElBQUksQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDckMsSUFBSSxTQUFTLEVBQUUsQ0FBQztZQUNkLE9BQU8sTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pDLENBQUM7UUFFRCxNQUFNLFlBQVksR0FBRyxNQUFNLElBQUksQ0FBQyxzQkFBc0IsQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO1FBQzVFLE1BQU0sUUFBUSxHQUFHLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxlQUFlLENBQUMsWUFBWSxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO1FBQzNGLElBQUksUUFBUSxFQUFFLENBQUM7WUFDYixPQUFPLFFBQVEsQ0FBQztRQUNsQixDQUFDO1FBRUQsTUFBTSxlQUFlLEdBQUcsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMscUJBQXFCLEVBQUUsQ0FBQztRQUNsRixNQUFNLFdBQVcsR0FBRyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsZUFBZSxDQUFDLGVBQWUsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztRQUVqRyxPQUFPLFdBQVcsQ0FBQztJQUNyQixDQUFDO0lBRUQsS0FBSyxDQUFDLGNBQWMsQ0FBQyxRQUFrQixFQUFFLElBQVk7UUFDbkQsT0FBTyxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsZUFBZSxDQUFDLFFBQVEsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUMvRSxDQUFDO0lBRUQsS0FBSyxDQUFDLGNBQWMsQ0FBQyxJQUFZO1FBQy9CLE9BQU8sTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUN4RCxDQUFDO0lBRUQsS0FBSyxDQUFDLEtBQUssQ0FBQyxJQUFZLEVBQUUsUUFBZ0I7UUFDeEMsTUFBTSxJQUFJLEdBQUcsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUM1RCxJQUFJLENBQUMsSUFBSTtZQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsZUFBZSxDQUFDLFlBQVksRUFBRSxDQUFDO1FBQ3pELElBQUksQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLElBQUksRUFBRSxRQUFRLENBQUMsRUFBRSxDQUFDO1lBQ3hDLE9BQU8sRUFBRSxJQUFJLEVBQUUsZUFBZSxDQUFDLElBQUksRUFBRSxDQUFDO1FBQ3hDLENBQUM7UUFDRCxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2xELE9BQU8sRUFBRSxJQUFJLEVBQUUsZUFBZSxDQUFDLE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDeEQsQ0FBQztJQUVELEtBQUssQ0FBQyxnQkFBZ0IsQ0FBQyxFQUFFLElBQUksRUFBRSxLQUFLLEVBQUUsRUFBRSxFQUFFLFFBQVEsR0FBRyxNQUFNLENBQUMsVUFBVSxFQUFFLEVBQW9DO1FBQzFHLElBQUksSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDMUQsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ1YsTUFBTSxTQUFTLEdBQUcsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDO2dCQUNsQyxJQUFJO2dCQUNKLEtBQUs7Z0JBQ0wsUUFBUTtnQkFDUixFQUFFO2FBQ0gsQ0FBQyxDQUFDO1lBQ0gsSUFBSSxHQUFHLFNBQVMsQ0FBQyxJQUFJLENBQUM7UUFDeEIsQ0FBQztRQUVELE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVELEtBQUssQ0FBQyxpQkFBaUIsQ0FBQyxJQUFzQztRQUM1RCxNQUFNLElBQUksR0FBRyxNQUFNLElBQUksQ0FBQyxnQkFBZ0IsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMvQyxNQUFNLEtBQUssR0FBRyxNQUFNLElBQUksQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxDQUFDO1FBQ2xELE9BQU8sRUFBRSxJQUFJLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDekIsQ0FBQztJQUVELEtBQUssQ0FBQyxNQUFNLENBQUMsVUFBc0I7UUFDakMsTUFBTSxZQUFZLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDNUQsTUFBTSxLQUFLLEdBQUcsR0FBRyxZQUFZLEdBQUcsVUFBVSxDQUFDLFFBQVEsRUFBRSxDQUFDO1FBQ3RELE1BQU0saUJBQWlCLEdBQUcsU0FBUyxDQUFDLEtBQUssQ0FBQyxDQUFDO1FBQzNDLE1BQU0sVUFBVSxHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQUM7WUFDbkMsSUFBSSxFQUFFLFVBQVUsQ0FBQyxJQUFJO1lBQ3JCLEtBQUssRUFBRSxVQUFVLENBQUMsS0FBSztZQUN2QixFQUFFLEVBQUUsVUFBVSxDQUFDLEVBQUU7WUFDakIsWUFBWTtZQUNaLGlCQUFpQjtZQUNqQixTQUFTLEVBQUUsSUFBSTtTQUNoQixDQUFDLENBQUM7UUFDSCxNQUFNLElBQUksQ0FBQyxjQUFjLENBQUMsUUFBUSxDQUFDLFVBQVUsQ0FBQyxDQUFDO1FBQy9DLE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLFdBQVcsQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDeEQsT0FBTyxFQUFFLElBQUksRUFBRSxVQUFVLEVBQUUsS0FBSyxFQUFFLENBQUM7SUFDckMsQ0FBQztJQUVELEtBQUssQ0FBQyxRQUFRLENBQUMsSUFBWSxFQUFFLEtBQWEsRUFBRSxVQUFVLEdBQUcsTUFBTTtRQUM3RCxNQUFNLFNBQVMsR0FBRyxJQUFJLENBQUMsVUFBVSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsVUFBVSxHQUFHLElBQUksRUFBRSxDQUFDO1FBQzNFLElBQUksSUFBSSxHQUFHLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxjQUFjLENBQUMsU0FBUyxDQUFDLENBQUM7UUFDL0QsSUFBSSxDQUFDLElBQUksRUFBRSxDQUFDO1lBQ1YsTUFBTSxZQUFZLEdBQUcsTUFBTSxDQUFDLFdBQVcsQ0FBQyxFQUFFLENBQUMsQ0FBQyxRQUFRLENBQUMsS0FBSyxDQUFDLENBQUM7WUFDNUQsTUFBTSxpQkFBaUIsR0FBRyxTQUFTLENBQUMsWUFBWSxDQUFDLENBQUM7WUFDbEQsSUFBSSxHQUFHLFVBQVUsQ0FBQyxNQUFNLENBQUM7Z0JBQ3ZCLElBQUksRUFBRSxTQUFTO2dCQUNmLEtBQUs7Z0JBQ0wsRUFBRSxFQUFFLEVBQUU7Z0JBQ04sWUFBWTtnQkFDWixpQkFBaUI7Z0JBQ2pCLFNBQVMsRUFBRSxLQUFLO2FBQ2pCLENBQUMsQ0FBQztZQUNILE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7WUFDekMsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDakMsQ0FBQztRQUNELElBQUksSUFBSSxDQUFDLEtBQUssS0FBSyxLQUFLLEVBQUUsQ0FBQztZQUN6QixPQUFPO1lBQ1AsT0FBTyxFQUFFLE9BQU8sRUFBRSxLQUFLLEVBQUUsSUFBSSxFQUFFLENBQUM7UUFDbEMsQ0FBQztRQUNELElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ25CLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxRQUFRLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDekMsT0FBTyxFQUFFLE9BQU8sRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLENBQUM7SUFDakMsQ0FBQztJQUVELEtBQUssQ0FBQyxXQUFXLENBQUMsTUFBYyxFQUFFLFVBQTZCLEVBQUU7UUFDL0QsMEVBQTBFO1FBQzFFLGtGQUFrRjtRQUNsRiwwR0FBMEc7UUFDMUcsTUFBTSxLQUFLLEdBQUcsV0FBVyxDQUFDLElBQUksQ0FBQyxNQUFNLENBQUMsUUFBUSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3JELE1BQU0sUUFBUSxHQUFHLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQztRQUMvQixNQUFNLFNBQVMsR0FBRyxLQUFLLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxLQUFLLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO1FBQ3pELE1BQU0sV0FBVyxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUM7WUFDckMsUUFBUTtZQUNSLFNBQVM7WUFDVCxNQUFNO1lBQ04sR0FBRyxPQUFPO1NBQ1gsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLFNBQVMsQ0FBQyxXQUFXLENBQUMsQ0FBQztRQUNqRCxXQUFXLENBQUMsS0FBSyxHQUFHLEtBQUssQ0FBQztRQUMxQixPQUFPLFdBQVcsQ0FBQztJQUNyQixDQUFDO0lBRUQsS0FBSyxDQUFDLFdBQVcsQ0FBQyxNQUFjLEVBQUUsb0JBQTRCO1FBQzVELElBQUksS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxtQkFBbUIsQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDO1FBQ2hGLElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNYLDZEQUE2RDtZQUM3RCxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLG1CQUFtQixDQUFDLE1BQU0sQ0FBQyxvQkFBb0IsQ0FBQyxDQUFDLENBQUM7UUFDdEYsQ0FBQztRQUNELElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztZQUNYLE1BQU0sSUFBSSxhQUFhLENBQUMsVUFBVSxvQkFBb0IsY0FBYyxDQUFDLENBQUM7UUFDeEUsQ0FBQztRQUNELElBQUksS0FBSyxDQUFDLE1BQU0sS0FBSyxNQUFNLEVBQUUsQ0FBQztZQUM1QixNQUFNLElBQUksY0FBYyxDQUFDLG1DQUFtQyxvQkFBb0IsR0FBRyxDQUFDLENBQUM7UUFDdkYsQ0FBQztRQUNELE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxXQUFXLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO0lBQ3ZELENBQUM7SUFFRCxLQUFLLENBQUMsc0JBQXNCLENBQUMsTUFBYyxFQUFFLFdBQXNDO1FBQ2pGLE1BQU0sVUFBVSxHQUFHLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxvQ0FBb0MsQ0FBQyxNQUFNLEVBQUUsV0FBVyxJQUFJLElBQUksQ0FBQyxDQUFDO1FBQy9HLE9BQU8sVUFBVSxDQUFDO0lBQ3BCLENBQUM7SUFFRCxLQUFLLENBQUMsd0JBQXdCLENBQUMsTUFBMEIsRUFBRSxPQUF3QztRQUNqRyxNQUFNLGdCQUFnQixHQUFHLHdCQUF3QixDQUFDLE1BQU0sQ0FBQztZQUN2RCxNQUFNLEVBQUUsTUFBZ0I7WUFDeEIsWUFBWSxFQUFFLE9BQU8sQ0FBQyxZQUFZO1lBQ2xDLFNBQVMsRUFBRSxPQUFPLENBQUMsU0FBUztZQUM1QixXQUFXLEVBQUUsT0FBTyxDQUFDLFdBQVc7U0FDakMsQ0FBQyxDQUFDO1FBQ0gsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLGNBQWMsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDO1FBQzNELE9BQU8sZ0JBQWdCLENBQUM7SUFDMUIsQ0FBQztJQUVELEtBQUssQ0FBQyx3QkFBd0IsQ0FBQyxNQUFlLEVBQUUsV0FBb0I7UUFDbEUsTUFBTSxVQUFVLEdBQUcsTUFBTSxJQUFJLENBQUMsY0FBYyxDQUFDLG9DQUFvQyxDQUFDLE1BQU0sRUFBRSxXQUFXLElBQUksSUFBSSxDQUFDLENBQUM7UUFDL0csSUFBSSxVQUFVLEVBQUUsQ0FBQztZQUNmLE1BQU0sSUFBSSxDQUFDLGNBQWMsQ0FBQyxnQkFBZ0IsQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLENBQUM7UUFDaEUsQ0FBQztJQUNILENBQUM7Q0FDRixDQUFBO0FBcEtrQjtJQURoQixNQUFNLEVBQUU7O21EQUN1QztBQUUvQjtJQURoQixNQUFNLEVBQUU7OzJEQUN1RDtBQUpyRCxXQUFXO0lBSHZCLGNBQWMsQ0FBQztRQUNkLFdBQVcsRUFBRSxXQUFXLENBQUMsTUFBTTtLQUNoQyxDQUFDO0dBQ1csV0FBVyxDQXNLdkIifQ==