Skip to content

Commit ec4af52

Browse files
Basic auth via config file (#451)
* Introduce encryption service * Add page for encrypting with public key * Handle basic auth for remote specifications * Bye bye proxy - use remotes endpoint now * Encapsulate encoding/decoding of RemoteConfig --------- Co-authored-by: Simon B. Støvring <[email protected]>
1 parent f368a66 commit ec4af52

File tree

18 files changed

+657
-146
lines changed

18 files changed

+657
-146
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,5 @@ GITHUB_CLIENT_ID=GitHub App client ID
2222
GITHUB_CLIENT_SECRET=GitHub App client secret
2323
GITHUB_APP_ID=123456
2424
GITHUB_PRIVATE_KEY_BASE_64=base 64 encoded version of the private key - see README.md for more info
25+
ENCRYPTION_PUBLIC_KEY_BASE_64=base 64 encoded version of the public key
26+
ENCRYPTION_PRIVATE_KEY_BASE_64=base 64 encoded version of the private key
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import RsaEncryptionService from '../../src/features/encrypt/EncryptionService'
2+
3+
const publicKey = `-----BEGIN PUBLIC KEY-----
4+
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1k4JT719AUz/wuXb2rt
5+
8933okfM2Iynmc6akSsZWEsW19byzO0UHp8b79xvsmNQKM1wBEBnXb5t+uLjJJZe
6+
rqCiTB7fBL64tExSKIDIRAlMnQtMfHs/rMgR+o/N2Yo2KimQw9G84goCEbBF2kbw
7+
5/MQfe43HeEoVWbNfgmRyP8VudO1UtVr07dGoUEWvFjudtd/h5H9THVdEpp2vH2Z
8+
pSGypn8hRAbOzhIM4ExLOH4ZHb8gPQGiHRGUYXk3Cy95RSf/SpEnRi0p4/63Nx5M
9+
JNXGM2Jk0RgGcYZcwJvLanT5Xdb9LM/IsDxLKXN+utDUgkzddvJbBC12aLaKaJA5
10+
LwIDAQAB
11+
-----END PUBLIC KEY-----`
12+
13+
const privateKey = `-----BEGIN PRIVATE KEY-----
14+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7WTglPvX0BTP/
15+
C5dvau3z3feiR8zYjKeZzpqRKxlYSxbX1vLM7RQenxvv3G+yY1AozXAEQGddvm36
16+
4uMkll6uoKJMHt8Evri0TFIogMhECUydC0x8ez+syBH6j83ZijYqKZDD0bziCgIR
17+
sEXaRvDn8xB97jcd4ShVZs1+CZHI/xW507VS1WvTt0ahQRa8WO5213+Hkf1MdV0S
18+
mna8fZmlIbKmfyFEBs7OEgzgTEs4fhkdvyA9AaIdEZRheTcLL3lFJ/9KkSdGLSnj
19+
/rc3Hkwk1cYzYmTRGAZxhlzAm8tqdPld1v0sz8iwPEspc3660NSCTN128lsELXZo
20+
topokDkvAgMBAAECggEAAWQMl0laQ8OZfiqWY72Ry0oYPgFvFO1PpkQHObm3+S+d
21+
8Q81IgXNLNtWKSA4VpXYQ4zcJUpADmg1ZdxAfszUB4kcshHdpz4Z9Y849i6KW4l4
22+
qZsP3hbQWtTbgYWG71+M+y2sqJu0hgCkLPmm31AsJDG6zPtEKokKbYH7jWV0Xo5z
23+
0g6IUqepc1ElNzsJAU10hgX5UZUPxvzbWHxhBhFzC51GKpfx/W5ZOQtB+W8+nlmC
24+
OSVlZ9pfr6qxOZbSLWESU1xplywPTPLoYs/38oN5OHIJvB2j8kl+JfcR7v2ezLeV
25+
fx1Z+x9ME0at7AbGCfhjIfJtftPsoCR60nzN3wWoAQKBgQDfOmfzLaWhVkvt49Hn
26+
zeLdLI8pwqWXVYozsPMRlExwuIT1KeNolPzWWKx6dG38UzY4XWSvq+w3WAcQ7m6E
27+
qiRWoRPL3qlWu3pDJYr/EfR2haPMQMwbJM/hg+nC0bhUSVqBEjOZgaQUHStIyugb
28+
SWQFI3jE9fgj71DtbiVNrb1vAQKBgQDW2ljkotAjF81vI+EoN9QmuPYnejo42nK9
29+
jlSEU4hrDQMLiqxc5yJidQh75vZRfaO9rdUqHxoXK0DEU3Jk16Kb0n4nkM+xqKoc
30+
yHTtAgUyflpenbrr4pRZf783XgI0bn/FhoMFQtAvSblru3NfEFQUtKIY82+Xa5H5
31+
g+cezSDYLwKBgBeViB39GJ6vC16azzZ6XhmX95gl5HDUrMFBVKzqyhiupf1w64HF
32+
G+FZhP97BZO/Bt91nomg1FgUiMqVJkAF6cjtQ7YqVCHBtO0bLlA8iWNsQx31Spsj
33+
jIL6+NuIZL0i8tjoH2N8euVVH5mVNmiLnHGeicflZM4HHrm3BWHrlTQBAoGBALeW
34+
W98CQFe8Pw542ixDiESOR8fz6UwrXWAb/pwTxL20oKV8GUxJNFhtKJK3CEMZ2JB7
35+
uWoEqYairvUTWOxSVeBQPPwSAWcNeE6f+0mKMGa1EQNIRDDLq3fOcNYevkOPKB7g
36+
kZQtQzclCAvGYQ8aJL6MmvY3DWOVx2YuD4+COE6BAoGAEGdChfJW5QGXaXEO/PnA
37+
PbQCCzcqbs+0O6LVR1w68H0WQww94tZjfWPqn9kvwjzLd22ZMmdiBJ3bEbDeCjmG
38+
Ybt48kS7y9n22CDgL7JkatszYpybvBSrDQL7ms7x2kKPkTMb7C5zpIIzdtvwH+Jf
39+
6K3kQbqfFCM7VmyR7AmoyOk=
40+
-----END PRIVATE KEY-----`
41+
42+
const encryptionService = new RsaEncryptionService({ publicKey, privateKey })
43+
44+
describe('RsaEncryptionService', () => {
45+
it('should encrypt and decrypt data correctly', () => {
46+
const data = 'Hello, World!'
47+
const encryptedData = encryptionService.encrypt(data)
48+
const decryptedData = encryptionService.decrypt(encryptedData)
49+
50+
expect(decryptedData).toBe(data)
51+
})
52+
53+
it('should throw an error when decrypting with incorrect data', () => {
54+
const incorrectData = 'invalidEncryptedData'
55+
56+
expect(() => {
57+
encryptionService.decrypt(incorrectData)
58+
}).toThrow()
59+
})
60+
61+
it('should throw an error when encrypting with an invalid public key', () => {
62+
const invalidPublicKey = 'invalidPublicKey'
63+
const invalidEncryptionService = new RsaEncryptionService({ publicKey: invalidPublicKey, privateKey })
64+
65+
expect(() => {
66+
invalidEncryptionService.encrypt('test')
67+
}).toThrow()
68+
})
69+
70+
it('should throw an error when decrypting with an invalid private key', () => {
71+
const data = 'Hello, World!'
72+
const encryptedData = encryptionService.encrypt(data)
73+
const invalidPrivateKey = 'invalidPrivateKey'
74+
const invalidEncryptionService = new RsaEncryptionService({ publicKey, privateKey: invalidPrivateKey })
75+
76+
expect(() => {
77+
invalidEncryptionService.decrypt(encryptedData)
78+
}).toThrow()
79+
})
80+
})

__test__/projects/GitHubProjectDataSource.test.ts

Lines changed: 89 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,29 @@
11
import { GitHubProjectDataSource } from "@/features/projects/data"
2+
import RemoteConfig from "@/features/projects/domain/RemoteConfig"
3+
4+
/**
5+
* Simple encryption service for testing. Does nothing.
6+
*/
7+
const noopEncryptionService = {
8+
encrypt: function (data: string): string {
9+
return data
10+
},
11+
decrypt: function (encryptedDataBase64: string): string {
12+
return encryptedDataBase64
13+
}
14+
}
15+
16+
/**
17+
* Simple encoder for testing
18+
*/
19+
const base64RemoteConfigEncoder = {
20+
encode: function (remoteConfig: RemoteConfig): string {
21+
return Buffer.from(JSON.stringify(remoteConfig)).toString("base64")
22+
},
23+
decode: function (encodedString: string): RemoteConfig {
24+
return JSON.parse(Buffer.from(encodedString, "base64").toString())
25+
}
26+
}
227

328
test("It loads repositories from data source", async () => {
429
let didLoadRepositories = false
@@ -9,7 +34,9 @@ test("It loads repositories from data source", async () => {
934
didLoadRepositories = true
1035
return []
1136
}
12-
}
37+
},
38+
encryptionService: noopEncryptionService,
39+
remoteConfigEncoder: base64RemoteConfigEncoder
1340
})
1441
await sut.getProjects()
1542
expect(didLoadRepositories).toBeTruthy()
@@ -43,7 +70,9 @@ test("It maps projects including branches and tags", async () => {
4370
}]
4471
}]
4572
}
46-
}
73+
},
74+
encryptionService: noopEncryptionService,
75+
remoteConfigEncoder: base64RemoteConfigEncoder
4776
})
4877
const projects = await sut.getProjects()
4978
expect(projects).toEqual([{
@@ -107,7 +136,9 @@ test("It removes suffix from project name", async () => {
107136
}]
108137
}]
109138
}
110-
}
139+
},
140+
encryptionService: noopEncryptionService,
141+
remoteConfigEncoder: base64RemoteConfigEncoder
111142
})
112143
const projects = await sut.getProjects()
113144
expect(projects[0].id).toEqual("acme-foo")
@@ -147,7 +178,9 @@ test("It supports multiple OpenAPI specifications on a branch", async () => {
147178
}]
148179
}]
149180
}
150-
}
181+
},
182+
encryptionService: noopEncryptionService,
183+
remoteConfigEncoder: base64RemoteConfigEncoder
151184
})
152185
const projects = await sut.getProjects()
153186
expect(projects).toEqual([{
@@ -209,7 +242,9 @@ test("It filters away projects with no versions", async () => {
209242
tags: []
210243
}]
211244
}
212-
}
245+
},
246+
encryptionService: noopEncryptionService,
247+
remoteConfigEncoder: base64RemoteConfigEncoder
213248
})
214249
const projects = await sut.getProjects()
215250
expect(projects.length).toEqual(0)
@@ -243,7 +278,9 @@ test("It filters away branches with no specifications", async () => {
243278
tags: []
244279
}]
245280
}
246-
}
281+
},
282+
encryptionService: noopEncryptionService,
283+
remoteConfigEncoder: base64RemoteConfigEncoder
247284
})
248285
const projects = await sut.getProjects()
249286
expect(projects[0].versions.length).toEqual(1)
@@ -283,7 +320,9 @@ test("It filters away tags with no specifications", async () => {
283320
}]
284321
}]
285322
}
286-
}
323+
},
324+
encryptionService: noopEncryptionService,
325+
remoteConfigEncoder: base64RemoteConfigEncoder
287326
})
288327
const projects = await sut.getProjects()
289328
expect(projects[0].versions.length).toEqual(2)
@@ -314,7 +353,9 @@ test("It reads image from configuration file with .yml extension", async () => {
314353
tags: []
315354
}]
316355
}
317-
}
356+
},
357+
encryptionService: noopEncryptionService,
358+
remoteConfigEncoder: base64RemoteConfigEncoder
318359
})
319360
const projects = await sut.getProjects()
320361
expect(projects[0].imageURL).toEqual("/api/blob/acme/foo-openapi/icon.png?ref=12345678")
@@ -345,7 +386,9 @@ test("It reads display name from configuration file with .yml extension", async
345386
tags: []
346387
}]
347388
}
348-
}
389+
},
390+
encryptionService: noopEncryptionService,
391+
remoteConfigEncoder: base64RemoteConfigEncoder
349392
})
350393
const projects = await sut.getProjects()
351394
expect(projects[0].id).toEqual("acme-foo")
@@ -378,7 +421,9 @@ test("It reads image from configuration file with .yaml extension", async () =>
378421
tags: []
379422
}]
380423
}
381-
}
424+
},
425+
encryptionService: noopEncryptionService,
426+
remoteConfigEncoder: base64RemoteConfigEncoder
382427
})
383428
const projects = await sut.getProjects()
384429
expect(projects[0].imageURL).toEqual("/api/blob/acme/foo-openapi/icon.png?ref=12345678")
@@ -409,7 +454,9 @@ test("It reads display name from configuration file with .yaml extension", async
409454
tags: []
410455
}]
411456
}
412-
}
457+
},
458+
encryptionService: noopEncryptionService,
459+
remoteConfigEncoder: base64RemoteConfigEncoder
413460
})
414461
const projects = await sut.getProjects()
415462
expect(projects[0].id).toEqual("acme-foo")
@@ -478,7 +525,9 @@ test("It sorts projects alphabetically", async () => {
478525
tags: []
479526
}]
480527
}
481-
}
528+
},
529+
encryptionService: noopEncryptionService,
530+
remoteConfigEncoder: base64RemoteConfigEncoder
482531
})
483532
const projects = await sut.getProjects()
484533
expect(projects[0].name).toEqual("anne")
@@ -529,7 +578,9 @@ test("It sorts versions alphabetically", async () => {
529578
}]
530579
}]
531580
}
532-
}
581+
},
582+
encryptionService: noopEncryptionService,
583+
remoteConfigEncoder: base64RemoteConfigEncoder
533584
})
534585
const projects = await sut.getProjects()
535586
expect(projects[0].versions[0].name).toEqual("1.0")
@@ -593,7 +644,9 @@ test("It prioritizes main, master, develop, and development branch names when so
593644
}]
594645
}]
595646
}
596-
}
647+
},
648+
encryptionService: noopEncryptionService,
649+
remoteConfigEncoder: base64RemoteConfigEncoder
597650
})
598651
const projects = await sut.getProjects()
599652
expect(projects[0].versions[0].name).toEqual("main")
@@ -641,7 +694,9 @@ test("It identifies the default branch in returned versions", async () => {
641694
tags: []
642695
}]
643696
}
644-
}
697+
},
698+
encryptionService: noopEncryptionService,
699+
remoteConfigEncoder: base64RemoteConfigEncoder
645700
})
646701
const projects = await sut.getProjects()
647702
const defaultVersionNames = projects[0]
@@ -682,7 +737,9 @@ test("It adds remote versions from the project configuration", async () => {
682737
tags: []
683738
}]
684739
}
685-
}
740+
},
741+
encryptionService: noopEncryptionService,
742+
remoteConfigEncoder: base64RemoteConfigEncoder
686743
})
687744
const projects = await sut.getProjects()
688745
expect(projects[0].versions).toEqual([{
@@ -692,11 +749,11 @@ test("It adds remote versions from the project configuration", async () => {
692749
specifications: [{
693750
id: "huey",
694751
name: "Huey",
695-
url: `/api/proxy?url=${encodeURIComponent("https://example.com/huey.yml")}`
752+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/huey.yml" })}`
696753
}, {
697754
id: "dewey",
698755
name: "Dewey",
699-
url: `/api/proxy?url=${encodeURIComponent("https://example.com/dewey.yml")}`
756+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/dewey.yml" })}`
700757
}]
701758
}, {
702759
id: "bobby",
@@ -705,7 +762,7 @@ test("It adds remote versions from the project configuration", async () => {
705762
specifications: [{
706763
id: "louie",
707764
name: "Louie",
708-
url: `/api/proxy?url=${encodeURIComponent("https://example.com/louie.yml")}`
765+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/louie.yml" })}`
709766
}]
710767
}])
711768
})
@@ -745,7 +802,9 @@ test("It modifies ID of remote version if the ID already exists", async () => {
745802
tags: []
746803
}]
747804
}
748-
}
805+
},
806+
encryptionService: noopEncryptionService,
807+
remoteConfigEncoder: base64RemoteConfigEncoder
749808
})
750809
const projects = await sut.getProjects()
751810
expect(projects[0].versions).toEqual([{
@@ -766,7 +825,7 @@ test("It modifies ID of remote version if the ID already exists", async () => {
766825
specifications: [{
767826
id: "baz",
768827
name: "Baz",
769-
url: `/api/proxy?url=${encodeURIComponent("https://example.com/baz.yml")}`
828+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
770829
}]
771830
}, {
772831
id: "bar2",
@@ -775,7 +834,7 @@ test("It modifies ID of remote version if the ID already exists", async () => {
775834
specifications: [{
776835
id: "hello",
777836
name: "Hello",
778-
url: `/api/proxy?url=${encodeURIComponent("https://example.com/hello.yml")}`
837+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/hello.yml" })}`
779838
}]
780839
}])
781840
})
@@ -806,7 +865,9 @@ test("It lets users specify the ID of a remote version", async () => {
806865
tags: []
807866
}]
808867
}
809-
}
868+
},
869+
encryptionService: noopEncryptionService,
870+
remoteConfigEncoder: base64RemoteConfigEncoder
810871
})
811872
const projects = await sut.getProjects()
812873
expect(projects[0].versions).toEqual([{
@@ -816,7 +877,7 @@ test("It lets users specify the ID of a remote version", async () => {
816877
specifications: [{
817878
id: "baz",
818879
name: "Baz",
819-
url: `/api/proxy?url=${encodeURIComponent("https://example.com/baz.yml")}`
880+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
820881
}]
821882
}])
822883
})
@@ -847,7 +908,9 @@ test("It lets users specify the ID of a remote specification", async () => {
847908
tags: []
848909
}]
849910
}
850-
}
911+
},
912+
encryptionService: noopEncryptionService,
913+
remoteConfigEncoder: base64RemoteConfigEncoder
851914
})
852915
const projects = await sut.getProjects()
853916
expect(projects[0].versions).toEqual([{
@@ -857,7 +920,7 @@ test("It lets users specify the ID of a remote specification", async () => {
857920
specifications: [{
858921
id: "some-spec",
859922
name: "Baz",
860-
url: `/api/proxy?url=${encodeURIComponent("https://example.com/baz.yml")}`
923+
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
861924
}]
862925
}])
863926
})

0 commit comments

Comments
 (0)