Skip to content

Commit b671ef3

Browse files
committed
Auth context types sketched out - tests fail as not every request is authenticated
1 parent 3a1a04b commit b671ef3

File tree

5 files changed

+113
-95
lines changed

5 files changed

+113
-95
lines changed

src/git.test.ts

+27-20
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ describe('git', () => {
2929
fs.mkdirSync(srcDir, '0700');
3030
fs.mkdirSync(dstDir, '0700');
3131

32-
const repos = new Git(repoDir, {
32+
const repos = new Git<number>(repoDir, {
3333
autoCreate: true,
34+
authenticate: () => 42,
3435
});
3536
const port = Math.floor(Math.random() * ((1 << 16) - 1e4)) + 1e4;
3637
const server = http
@@ -42,6 +43,8 @@ describe('git', () => {
4243
process.chdir(srcDir);
4344

4445
repos.on('push', (push) => {
46+
expect(push.context).toBe(42);
47+
4548
expect(push.repo).toBe('xyz/doom');
4649
expect(push.commit).toBe(lastCommit);
4750
expect(push.branch).toBe('master');
@@ -655,17 +658,16 @@ describe('git', () => {
655658

656659
const repos = new Git(repoDir, {
657660
autoCreate: true,
658-
authenticate: ({ type, repo, user }, next) => {
661+
authenticate: async ({ type, repo, user }) => {
659662
if (type === 'fetch' && repo === 'doom') {
660-
user((username, password) => {
661-
if (username == 'root' && password == 'root') {
662-
next();
663-
} else {
664-
next(new Error('that is not the correct password'));
665-
}
666-
});
663+
const [username, password] = await user();
664+
if (username == 'root' && password == 'root') {
665+
return;
666+
} else {
667+
throw new Error('that is not the correct password');
668+
}
667669
} else {
668-
next(new Error('that is not the correct password'));
670+
throw new Error('that is not the correct password');
669671
}
670672
},
671673
});
@@ -721,25 +723,30 @@ describe('git', () => {
721723
fs.mkdirSync(srcDir, '0700');
722724
fs.mkdirSync(dstDir, '0700');
723725

724-
const repos = new Git(repoDir, {
726+
interface Context {
727+
username: string;
728+
}
729+
730+
const repos = new Git<Context>(repoDir, {
725731
autoCreate: true,
726-
authenticate: ({ type, repo, user, headers }, next) => {
732+
authenticate: async ({ type, repo, user, headers }) => {
727733
if (type === 'fetch' && repo === 'doom') {
728734
expect(headers['host']).toBeTruthy();
729735
expect(headers['user-agent']).toBeTruthy();
730736
expect(headers['accept']).toBeTruthy();
731737
expect(headers['pragma']).toBeTruthy();
732738
expect(headers['accept-encoding']).toBeTruthy();
733739

734-
user((username, password) => {
735-
if (username == 'root' && password == 'root') {
736-
next();
737-
} else {
738-
next(new Error('that is not the correct password'));
739-
}
740-
});
740+
const [username, password] = await user();
741+
if (username == 'root' && password == 'root') {
742+
return {
743+
username: username,
744+
};
745+
} else {
746+
throw new Error('that is not the correct password');
747+
}
741748
} else {
742-
next(new Error('that is not the correct password'));
749+
throw new Error('that is not the correct password');
743750
}
744751
},
745752
});

src/git.ts

+69-52
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,9 @@ interface GitServerOptions extends ServerOptions {
2525
type: 'http' | 'https';
2626
}
2727

28-
export interface GitOptions {
28+
export interface GitOptions<T = undefined> {
2929
autoCreate?: boolean;
30-
authenticate?: (
31-
options: GitAuthenticateOptions,
32-
callback: (error?: Error) => void | undefined
33-
) => void | Promise<Error | undefined | void> | undefined;
30+
authenticate?: (options: GitAuthenticateOptions) => Promise<T> | T;
3431
checkout?: boolean;
3532
}
3633

@@ -50,44 +47,49 @@ export interface GitAuthenticateOptions {
5047
/**
5148
* An http duplex object (see below) with these extra properties:
5249
*/
53-
export interface TagData extends HttpDuplex {
50+
export interface TagData<T = any> extends HttpDuplex {
5451
repo: string; // The string that defines the repo
5552
commit: string; // The string that defines the commit sha
5653
version: string; // The string that defines the tag being pushed
54+
context?: T;
5755
}
5856

5957
/**
6058
* Is a http duplex object (see below) with these extra properties
6159
*/
62-
export interface PushData extends HttpDuplex {
60+
export interface PushData<T = any> extends HttpDuplex {
6361
repo: string; // The string that defines the repo
6462
commit: string; // The string that defines the commit sha
6563
branch: string; // The string that defines the branch
64+
context?: T;
6665
}
6766

6867
/**
6968
* an http duplex object (see below) with these extra properties
7069
*/
71-
export interface FetchData extends HttpDuplex {
70+
export interface FetchData<T = any> extends HttpDuplex {
7271
repo: string; // The string that defines the repo
7372
commit: string; // The string that defines the commit sha
73+
context?: T;
7474
}
7575

7676
/**
7777
* an http duplex object (see below) with these extra properties
7878
*/
79-
export interface InfoData extends HttpDuplex {
79+
export interface InfoData<T = any> extends HttpDuplex {
8080
repo: string; // The string that defines the repo
81+
context?: T;
8182
}
8283

8384
/**
8485
* an http duplex object (see below) with these extra properties
8586
*/
86-
export interface HeadData extends HttpDuplex {
87+
export interface HeadData<T = any> extends HttpDuplex {
8788
repo: string; // The string that defines the repo
89+
context?: T;
8890
}
8991

90-
export interface GitEvents {
92+
export interface GitEvents<T = any> {
9193
/**
9294
* @example
9395
* repos.on('push', function (push) { ... }
@@ -97,7 +99,7 @@ export interface GitEvents {
9799
* Exactly one listener must call `push.accept()` or `push.reject()`. If there are
98100
* no listeners, `push.accept()` is called automatically.
99101
**/
100-
on(event: 'push', listener: (push: PushData) => void): this;
102+
on(event: 'push', listener: (push: PushData<T>) => void): this;
101103

102104
/**
103105
* @example
@@ -107,7 +109,7 @@ export interface GitEvents {
107109
* Exactly one listener must call `tag.accept()` or `tag.reject()`. If there are
108110
* No listeners, `tag.accept()` is called automatically.
109111
**/
110-
on(event: 'tag', listener: (tag: TagData) => void): this;
112+
on(event: 'tag', listener: (tag: TagData<T>) => void): this;
111113

112114
/**
113115
* @example
@@ -119,7 +121,7 @@ export interface GitEvents {
119121
* Exactly one listener must call `fetch.accept()` or `fetch.reject()`. If there are
120122
* no listeners, `fetch.accept()` is called automatically.
121123
**/
122-
on(event: 'fetch', listener: (fetch: FetchData) => void): this;
124+
on(event: 'fetch', listener: (fetch: FetchData<T>) => void): this;
123125

124126
/**
125127
* @example
@@ -130,7 +132,7 @@ export interface GitEvents {
130132
* Exactly one listener must call `info.accept()` or `info.reject()`. If there are
131133
* no listeners, `info.accept()` is called automatically.
132134
**/
133-
on(event: 'info', listener: (info: InfoData) => void): this;
135+
on(event: 'info', listener: (info: InfoData<T>) => void): this;
134136

135137
/**
136138
* @example
@@ -142,17 +144,14 @@ export interface GitEvents {
142144
* no listeners, `head.accept()` is called automatically.
143145
*
144146
**/
145-
on(event: 'head', listener: (head: HeadData) => void): this;
147+
on(event: 'head', listener: (head: HeadData<T>) => void): this;
146148
}
147-
export class Git extends EventEmitter implements GitEvents {
149+
export class Git<T = any> extends EventEmitter implements GitEvents {
148150
dirMap: (dir?: string) => string;
149151

150152
authenticate:
151-
| ((
152-
options: GitAuthenticateOptions,
153-
callback: (error?: Error) => void | undefined
154-
) => void | Promise<Error | undefined | void> | undefined)
155-
| undefined;
153+
| ((options: GitAuthenticateOptions) => Promise<T> | T)
154+
| undefined = undefined;
156155

157156
autoCreate: boolean;
158157
checkout: boolean | undefined;
@@ -185,7 +184,7 @@ export class Git extends EventEmitter implements GitEvents {
185184
*/
186185
constructor(
187186
repoDir: string | ((dir?: string) => string),
188-
options: GitOptions = {}
187+
options: GitOptions<T> = {}
189188
) {
190189
super();
191190

@@ -199,9 +198,7 @@ export class Git extends EventEmitter implements GitEvents {
199198
};
200199
}
201200

202-
if (options.authenticate) {
203-
this.authenticate = options.authenticate;
204-
}
201+
this.authenticate = options.authenticate;
205202

206203
this.autoCreate = options.autoCreate === false ? false : true;
207204
this.checkout = options.checkout;
@@ -253,13 +250,13 @@ export class Git extends EventEmitter implements GitEvents {
253250
* @param callback - Optionally get a callback `cb(err)` to be notified when the repository was created.
254251
*/
255252
create(repo: string, callback: (error?: Error) => void) {
256-
function next(self: Git) {
253+
const next = () => {
257254
let ps;
258255
let _error = '';
259256

260-
const dir = self.dirMap(repo);
257+
const dir = this.dirMap(repo);
261258

262-
if (self.checkout) {
259+
if (this.checkout) {
263260
ps = spawn('git', ['init', dir]);
264261
} else {
265262
ps = spawn('git', ['init', '--bare', dir]);
@@ -278,7 +275,7 @@ export class Git extends EventEmitter implements GitEvents {
278275
callback();
279276
}
280277
});
281-
}
278+
};
282279

283280
if (typeof callback !== 'function')
284281
callback = () => {
@@ -293,7 +290,7 @@ export class Git extends EventEmitter implements GitEvents {
293290
this.mkdir(repo);
294291
}
295292

296-
next(this);
293+
next();
297294
}
298295
/**
299296
* returns the typeof service being process. This will respond with either fetch or push.
@@ -319,6 +316,8 @@ export class Git extends EventEmitter implements GitEvents {
319316
// eslint-disable-next-line @typescript-eslint/no-this-alias
320317
const self = this;
321318

319+
let context: T | undefined = undefined;
320+
322321
const handlers = [
323322
(req: http.IncomingMessage, res: http.ServerResponse) => {
324323
if (req.method !== 'GET') return false;
@@ -367,27 +366,40 @@ export class Git extends EventEmitter implements GitEvents {
367366
const headers = req.headers;
368367
const user = (
369368
callback?: (username?: string, password?: string) => void
370-
) =>
371-
callback
372-
? basicAuth(req, res, callback)
373-
: new Promise<[string | undefined, string | undefined]>(
374-
(resolve) => basicAuth(req, res, (u, p) => resolve([u, p]))
375-
);
376-
377-
const promise = this.authenticate(
378-
{
379-
type,
380-
repo: repoName,
381-
user: user as unknown as GitAuthenticateOptions['user'],
382-
headers,
383-
},
384-
(error?: Error) => {
385-
return next(error);
369+
) => {
370+
const basicAuthResult = basicAuth(req, res);
371+
if (basicAuthResult && callback) {
372+
callback(...basicAuthResult);
373+
}
374+
if (basicAuthResult) {
375+
return new Promise<[string | undefined, string | undefined]>(
376+
(resolve) => resolve(basicAuthResult)
377+
);
386378
}
387-
);
379+
// return new Promise<[string | undefined, string | undefined]>(
380+
// (_, reject) => reject(new Error("Basic auth failed"))
381+
// );
382+
return new Promise<[string | undefined, string | undefined]>(
383+
() => {}
384+
);
385+
};
386+
387+
const promise = this.authenticate({
388+
type,
389+
repo: repoName,
390+
user: user,
391+
headers,
392+
});
388393

389394
if (promise instanceof Promise) {
390-
return promise.then(next).catch(next);
395+
return promise
396+
.then((ctx) => {
397+
context = ctx;
398+
next();
399+
})
400+
.catch(next);
401+
} else {
402+
context = promise;
391403
}
392404
} else {
393405
return next();
@@ -467,14 +479,15 @@ export class Git extends EventEmitter implements GitEvents {
467479
);
468480
noCache(res);
469481

470-
const action = createAction(
482+
const action = createAction<T>(
471483
{
472484
repo: repo,
473485
service: service as ServiceString,
474486
cwd: self.dirMap(repo),
475487
},
476488
req,
477-
res
489+
res,
490+
context
478491
);
479492

480493
action.on('header', () => {
@@ -515,7 +528,11 @@ export class Git extends EventEmitter implements GitEvents {
515528
* @param options.cert - the cert file for the https server
516529
* @param callback - the function to call when server is started or error has occurred
517530
*/
518-
listen(port: number, options?: GitServerOptions, callback?: () => void): Git {
531+
listen(
532+
port: number,
533+
options?: GitServerOptions,
534+
callback?: () => void
535+
): this {
519536
if (!options) {
520537
options = { type: 'http' };
521538
}

src/service.ts

+3-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ export interface ServiceOptions {
2424
service: ServiceString;
2525
}
2626

27-
export class Service extends HttpDuplex {
27+
export class Service<T> extends HttpDuplex {
2828
status: string;
2929
repo: string;
3030
service: string;
@@ -44,7 +44,8 @@ export class Service extends HttpDuplex {
4444
constructor(
4545
opts: ServiceOptions,
4646
req: http.IncomingMessage,
47-
res: http.ServerResponse
47+
res: http.ServerResponse,
48+
public context: T | undefined
4849
) {
4950
super(req, res);
5051

0 commit comments

Comments
 (0)