From 43223095cb277f4ba5565ecd2f49588013f35dd5 Mon Sep 17 00:00:00 2001 From: Dejan Lukic Date: Wed, 10 Mar 2021 16:07:52 +1100 Subject: [PATCH 1/2] Cache GitLab responses to avoid spamming the GitLab server --- src/gitlab.ts | 48 ++++++++++++++++++++++++++++++++++++++-------- src/gitlabcache.ts | 44 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+), 8 deletions(-) create mode 100644 src/gitlabcache.ts diff --git a/src/gitlab.ts b/src/gitlab.ts index b1a15f8..34248b8 100644 --- a/src/gitlab.ts +++ b/src/gitlab.ts @@ -7,6 +7,7 @@ import Gitlab from 'gitlab'; import { UserDataGroups } from './authcache'; import { AuthCache, UserData } from './authcache'; +import { GitlabCache } from "./gitlabcache"; export type VerdaccioGitlabAccessLevel = '$guest' | '$reporter' | '$developer' | '$maintainer' | '$owner'; @@ -42,6 +43,7 @@ export default class VerdaccioGitLab implements IPluginAuth; private config: VerdaccioGitlabConfig; private authCache?: AuthCache; + private gitlabCache: GitlabCache; private logger: Logger; private publishLevel: VerdaccioGitlabAccessLevel; @@ -49,6 +51,8 @@ export default class VerdaccioGitLab implements IPluginAuth; + + this.gitlabCache.storePromise(user, password, 'user', promise); + } else { + this.logger.trace(`[gitlab] using cached promise for user: ${user}`); + } + + promise .then(response => { if (user.toLowerCase() !== response.username.toLowerCase()) { return cb(getUnauthorized('wrong gitlab username')); @@ -102,15 +118,31 @@ export default class VerdaccioGitLab implements IPluginAuth { - return groups.filter(group => group.path === group.full_path).map(group => group.path); - }); + groupsPromise = GitlabAPI.Groups.all(gitlabPublishQueryParams).then(groups => { + return groups.filter(group => group.path === group.full_path).map(group => group.path); + }); - const projectsPromise = GitlabAPI.Projects.all(gitlabPublishQueryParams).then(projects => { - return projects.map(project => project.path_with_namespace); - }); + this.gitlabCache.storePromise(user, password, 'groups', groupsPromise); + } else { + this.logger.trace('[gitlab] using cached promise for user groups with params:', gitlabPublishQueryParams.toString()); + } + + let projectsPromise = this.gitlabCache.getPromise(user, password, 'projects'); + if (!projectsPromise) { + this.logger.trace('[gitlab] querying gitlab user projects with params:', gitlabPublishQueryParams.toString()); + + projectsPromise = GitlabAPI.Projects.all(gitlabPublishQueryParams).then(projects => { + return projects.map(project => project.path_with_namespace); + }); + + this.gitlabCache.storePromise(user, password, 'projects', projectsPromise); + } else { + this.logger.trace('[gitlab] using cached promise for user projects with params:', gitlabPublishQueryParams.toString()); + } Promise.all([groupsPromise, projectsPromise]) .then(([groups, projectGroups]) => { diff --git a/src/gitlabcache.ts b/src/gitlabcache.ts new file mode 100644 index 0000000..a900fc6 --- /dev/null +++ b/src/gitlabcache.ts @@ -0,0 +1,44 @@ +// Copyright 2018 Roger Meier +// SPDX-License-Identifier: MIT + +import Crypto from 'crypto'; + +import {Logger} from '@verdaccio/types'; +import NodeCache from 'node-cache'; + +export class GitlabCache { + private logger: Logger; + private ttl: number; + private storage: NodeCache; + + public static get DEFAULT_TTL() { + return 300; + } + + private static _generateKeyHash(username: string, password: string) { + const sha = Crypto.createHash('sha256'); + sha.update(JSON.stringify({ username: username, password: password })); + return sha.digest('hex'); + } + + public constructor(logger: Logger, ttl?: number) { + this.logger = logger; + this.ttl = ttl || GitlabCache.DEFAULT_TTL; + + this.storage = new NodeCache({ + stdTTL: this.ttl, + useClones: false, + }); + this.storage.on('expired', (key, value) => { + this.logger.trace(`[gitlab] expired key: ${key} with value:`, value); + }); + } + + public getPromise(username: string, password: string, type: 'user' | 'groups' | 'projects'): Promise { + return this.storage.get(GitlabCache._generateKeyHash(`${username}_${type}_promise`, password)) as Promise; + } + + public storePromise(username: string, password: string, type: 'user' | 'groups' | 'projects', promise: Promise): boolean { + return this.storage.set(GitlabCache._generateKeyHash(`${username}_${type}_promise`, password), promise); + } +} From 99c0119fb0b41becdde4edb604a239a49cb04cd6 Mon Sep 17 00:00:00 2001 From: Dejan Lukic Date: Fri, 16 Apr 2021 08:42:16 +1000 Subject: [PATCH 2/2] Use es6 syntactical sugar --- src/gitlabcache.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/gitlabcache.ts b/src/gitlabcache.ts index a900fc6..071947d 100644 --- a/src/gitlabcache.ts +++ b/src/gitlabcache.ts @@ -11,13 +11,11 @@ export class GitlabCache { private ttl: number; private storage: NodeCache; - public static get DEFAULT_TTL() { - return 300; - } + public static readonly DEFAULT_TTL = 300; private static _generateKeyHash(username: string, password: string) { const sha = Crypto.createHash('sha256'); - sha.update(JSON.stringify({ username: username, password: password })); + sha.update(JSON.stringify({ username, password })); return sha.digest('hex'); }