Skip to content

Commit

Permalink
Cache GitLab responses to avoid spamming the GitLab server
Browse files Browse the repository at this point in the history
  • Loading branch information
dejan9393 committed Mar 10, 2021
1 parent 6b4a6db commit 6261a7b
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 8 deletions.
48 changes: 40 additions & 8 deletions src/gitlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -42,13 +43,16 @@ export default class VerdaccioGitLab implements IPluginAuth<VerdaccioGitlabConfi
private options: PluginOptions<VerdaccioGitlabConfig>;
private config: VerdaccioGitlabConfig;
private authCache?: AuthCache;
private gitlabCache: GitlabCache;
private logger: Logger;
private publishLevel: VerdaccioGitlabAccessLevel;

public constructor(config: VerdaccioGitlabConfig, options: PluginOptions<VerdaccioGitlabConfig>) {
this.logger = options.logger;
this.config = config;
this.options = options;
this.gitlabCache = new GitlabCache(this.logger, this.config.authCache?.ttl);

this.logger.info(`[gitlab] url: ${this.config.url}`);

if ((this.config.authCache || {}).enabled === false) {
Expand Down Expand Up @@ -89,7 +93,19 @@ export default class VerdaccioGitLab implements IPluginAuth<VerdaccioGitlabConfi
token: password,
});

GitlabAPI.Users.current()
// Check if we already have a stored promise
let promise = this.gitlabCache.getPromise(user, password, 'user');
if (!promise) {
this.logger.trace(`[gitlab] querying gitlab user: ${user}`);

promise = GitlabAPI.Users.current() as Promise<any>;

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'));
Expand All @@ -102,15 +118,31 @@ export default class VerdaccioGitLab implements IPluginAuth<VerdaccioGitlabConfi
// - for publish, the logged in user id and all the groups they can reach as configured with access level `$auth.gitlab.publish`
const gitlabPublishQueryParams = { min_access_level: publishLevelId };

this.logger.trace('[gitlab] querying gitlab user groups with params:', gitlabPublishQueryParams.toString());
let groupsPromise = this.gitlabCache.getPromise(user, password, 'groups');
if (!groupsPromise) {
this.logger.trace('[gitlab] querying gitlab user groups with params:', gitlabPublishQueryParams.toString());

groupsPromise = GitlabAPI.Groups.all(gitlabPublishQueryParams).then(groups => {
return groups.filter(group => group.path === group.full_path).map(group => group.path);
});

this.gitlabCache.storePromise(user, password, 'groups', groupsPromise);
}else {
this.logger.trace('[gitlab] using cached promise for user groups with params:', gitlabPublishQueryParams.toString());
}

const groupsPromise = GitlabAPI.Groups.all(gitlabPublishQueryParams).then(groups => {
return groups.filter(group => group.path === group.full_path).map(group => group.path);
});
let projectsPromise = this.gitlabCache.getPromise(user, password, 'projects');
if (!projectsPromise) {
this.logger.trace('[gitlab] querying gitlab user projects with params:', gitlabPublishQueryParams.toString());

const projectsPromise = GitlabAPI.Projects.all(gitlabPublishQueryParams).then(projects => {
return projects.map(project => project.path_with_namespace);
});
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]) => {
Expand Down
44 changes: 44 additions & 0 deletions src/gitlabcache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2018 Roger Meier <[email protected]>
// 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<any> {
return this.storage.get(GitlabCache._generateKeyHash(`${username}_${type}_promise`, password)) as Promise<any>;
}

public storePromise(username: string, password: string, type: 'user' | 'groups' | 'projects', promise: Promise<any>): boolean {
return this.storage.set(GitlabCache._generateKeyHash(`${username}_${type}_promise`, password), promise);
}
}

0 comments on commit 6261a7b

Please sign in to comment.