Skip to content

Commit 6261a7b

Browse files
committed
Cache GitLab responses to avoid spamming the GitLab server
1 parent 6b4a6db commit 6261a7b

File tree

2 files changed

+84
-8
lines changed

2 files changed

+84
-8
lines changed

src/gitlab.ts

+40-8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Gitlab from 'gitlab';
77

88
import { UserDataGroups } from './authcache';
99
import { AuthCache, UserData } from './authcache';
10+
import { GitlabCache } from "./gitlabcache";
1011

1112
export type VerdaccioGitlabAccessLevel = '$guest' | '$reporter' | '$developer' | '$maintainer' | '$owner';
1213

@@ -42,13 +43,16 @@ export default class VerdaccioGitLab implements IPluginAuth<VerdaccioGitlabConfi
4243
private options: PluginOptions<VerdaccioGitlabConfig>;
4344
private config: VerdaccioGitlabConfig;
4445
private authCache?: AuthCache;
46+
private gitlabCache: GitlabCache;
4547
private logger: Logger;
4648
private publishLevel: VerdaccioGitlabAccessLevel;
4749

4850
public constructor(config: VerdaccioGitlabConfig, options: PluginOptions<VerdaccioGitlabConfig>) {
4951
this.logger = options.logger;
5052
this.config = config;
5153
this.options = options;
54+
this.gitlabCache = new GitlabCache(this.logger, this.config.authCache?.ttl);
55+
5256
this.logger.info(`[gitlab] url: ${this.config.url}`);
5357

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

92-
GitlabAPI.Users.current()
96+
// Check if we already have a stored promise
97+
let promise = this.gitlabCache.getPromise(user, password, 'user');
98+
if (!promise) {
99+
this.logger.trace(`[gitlab] querying gitlab user: ${user}`);
100+
101+
promise = GitlabAPI.Users.current() as Promise<any>;
102+
103+
this.gitlabCache.storePromise(user, password, 'user', promise);
104+
}else {
105+
this.logger.trace(`[gitlab] using cached promise for user: ${user}`);
106+
}
107+
108+
promise
93109
.then(response => {
94110
if (user.toLowerCase() !== response.username.toLowerCase()) {
95111
return cb(getUnauthorized('wrong gitlab username'));
@@ -102,15 +118,31 @@ export default class VerdaccioGitLab implements IPluginAuth<VerdaccioGitlabConfi
102118
// - for publish, the logged in user id and all the groups they can reach as configured with access level `$auth.gitlab.publish`
103119
const gitlabPublishQueryParams = { min_access_level: publishLevelId };
104120

105-
this.logger.trace('[gitlab] querying gitlab user groups with params:', gitlabPublishQueryParams.toString());
121+
let groupsPromise = this.gitlabCache.getPromise(user, password, 'groups');
122+
if (!groupsPromise) {
123+
this.logger.trace('[gitlab] querying gitlab user groups with params:', gitlabPublishQueryParams.toString());
124+
125+
groupsPromise = GitlabAPI.Groups.all(gitlabPublishQueryParams).then(groups => {
126+
return groups.filter(group => group.path === group.full_path).map(group => group.path);
127+
});
128+
129+
this.gitlabCache.storePromise(user, password, 'groups', groupsPromise);
130+
}else {
131+
this.logger.trace('[gitlab] using cached promise for user groups with params:', gitlabPublishQueryParams.toString());
132+
}
106133

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

111-
const projectsPromise = GitlabAPI.Projects.all(gitlabPublishQueryParams).then(projects => {
112-
return projects.map(project => project.path_with_namespace);
113-
});
138+
projectsPromise = GitlabAPI.Projects.all(gitlabPublishQueryParams).then(projects => {
139+
return projects.map(project => project.path_with_namespace);
140+
});
141+
142+
this.gitlabCache.storePromise(user, password, 'projects', projectsPromise);
143+
}else {
144+
this.logger.trace('[gitlab] using cached promise for user projects with params:', gitlabPublishQueryParams.toString());
145+
}
114146

115147
Promise.all([groupsPromise, projectsPromise])
116148
.then(([groups, projectGroups]) => {

src/gitlabcache.ts

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright 2018 Roger Meier <[email protected]>
2+
// SPDX-License-Identifier: MIT
3+
4+
import Crypto from 'crypto';
5+
6+
import {Logger} from '@verdaccio/types';
7+
import NodeCache from 'node-cache';
8+
9+
export class GitlabCache {
10+
private logger: Logger;
11+
private ttl: number;
12+
private storage: NodeCache;
13+
14+
public static get DEFAULT_TTL() {
15+
return 300;
16+
}
17+
18+
private static _generateKeyHash(username: string, password: string) {
19+
const sha = Crypto.createHash('sha256');
20+
sha.update(JSON.stringify({ username: username, password: password }));
21+
return sha.digest('hex');
22+
}
23+
24+
public constructor(logger: Logger, ttl?: number) {
25+
this.logger = logger;
26+
this.ttl = ttl || GitlabCache.DEFAULT_TTL;
27+
28+
this.storage = new NodeCache({
29+
stdTTL: this.ttl,
30+
useClones: false,
31+
});
32+
this.storage.on('expired', (key, value) => {
33+
this.logger.trace(`[gitlab] expired key: ${key} with value:`, value);
34+
});
35+
}
36+
37+
public getPromise(username: string, password: string, type: 'user' | 'groups' | 'projects'): Promise<any> {
38+
return this.storage.get(GitlabCache._generateKeyHash(`${username}_${type}_promise`, password)) as Promise<any>;
39+
}
40+
41+
public storePromise(username: string, password: string, type: 'user' | 'groups' | 'projects', promise: Promise<any>): boolean {
42+
return this.storage.set(GitlabCache._generateKeyHash(`${username}_${type}_promise`, password), promise);
43+
}
44+
}

0 commit comments

Comments
 (0)