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..071947d --- /dev/null +++ b/src/gitlabcache.ts @@ -0,0 +1,42 @@ +// 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 readonly DEFAULT_TTL = 300; + + private static _generateKeyHash(username: string, password: string) { + const sha = Crypto.createHash('sha256'); + sha.update(JSON.stringify({ username, 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); + } +}