Skip to content

Commit

Permalink
Basic auth via config file (#451)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
ulrikandersen and simonbs authored Dec 5, 2024
1 parent f368a66 commit ec4af52
Show file tree
Hide file tree
Showing 18 changed files with 657 additions and 146 deletions.
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,5 @@ GITHUB_CLIENT_ID=GitHub App client ID
GITHUB_CLIENT_SECRET=GitHub App client secret
GITHUB_APP_ID=123456
GITHUB_PRIVATE_KEY_BASE_64=base 64 encoded version of the private key - see README.md for more info
ENCRYPTION_PUBLIC_KEY_BASE_64=base 64 encoded version of the public key
ENCRYPTION_PRIVATE_KEY_BASE_64=base 64 encoded version of the private key
80 changes: 80 additions & 0 deletions __test__/encrypt/EncryptionService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import RsaEncryptionService from '../../src/features/encrypt/EncryptionService'

const publicKey = `-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu1k4JT719AUz/wuXb2rt
8933okfM2Iynmc6akSsZWEsW19byzO0UHp8b79xvsmNQKM1wBEBnXb5t+uLjJJZe
rqCiTB7fBL64tExSKIDIRAlMnQtMfHs/rMgR+o/N2Yo2KimQw9G84goCEbBF2kbw
5/MQfe43HeEoVWbNfgmRyP8VudO1UtVr07dGoUEWvFjudtd/h5H9THVdEpp2vH2Z
pSGypn8hRAbOzhIM4ExLOH4ZHb8gPQGiHRGUYXk3Cy95RSf/SpEnRi0p4/63Nx5M
JNXGM2Jk0RgGcYZcwJvLanT5Xdb9LM/IsDxLKXN+utDUgkzddvJbBC12aLaKaJA5
LwIDAQAB
-----END PUBLIC KEY-----`

const privateKey = `-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQC7WTglPvX0BTP/
C5dvau3z3feiR8zYjKeZzpqRKxlYSxbX1vLM7RQenxvv3G+yY1AozXAEQGddvm36
4uMkll6uoKJMHt8Evri0TFIogMhECUydC0x8ez+syBH6j83ZijYqKZDD0bziCgIR
sEXaRvDn8xB97jcd4ShVZs1+CZHI/xW507VS1WvTt0ahQRa8WO5213+Hkf1MdV0S
mna8fZmlIbKmfyFEBs7OEgzgTEs4fhkdvyA9AaIdEZRheTcLL3lFJ/9KkSdGLSnj
/rc3Hkwk1cYzYmTRGAZxhlzAm8tqdPld1v0sz8iwPEspc3660NSCTN128lsELXZo
topokDkvAgMBAAECggEAAWQMl0laQ8OZfiqWY72Ry0oYPgFvFO1PpkQHObm3+S+d
8Q81IgXNLNtWKSA4VpXYQ4zcJUpADmg1ZdxAfszUB4kcshHdpz4Z9Y849i6KW4l4
qZsP3hbQWtTbgYWG71+M+y2sqJu0hgCkLPmm31AsJDG6zPtEKokKbYH7jWV0Xo5z
0g6IUqepc1ElNzsJAU10hgX5UZUPxvzbWHxhBhFzC51GKpfx/W5ZOQtB+W8+nlmC
OSVlZ9pfr6qxOZbSLWESU1xplywPTPLoYs/38oN5OHIJvB2j8kl+JfcR7v2ezLeV
fx1Z+x9ME0at7AbGCfhjIfJtftPsoCR60nzN3wWoAQKBgQDfOmfzLaWhVkvt49Hn
zeLdLI8pwqWXVYozsPMRlExwuIT1KeNolPzWWKx6dG38UzY4XWSvq+w3WAcQ7m6E
qiRWoRPL3qlWu3pDJYr/EfR2haPMQMwbJM/hg+nC0bhUSVqBEjOZgaQUHStIyugb
SWQFI3jE9fgj71DtbiVNrb1vAQKBgQDW2ljkotAjF81vI+EoN9QmuPYnejo42nK9
jlSEU4hrDQMLiqxc5yJidQh75vZRfaO9rdUqHxoXK0DEU3Jk16Kb0n4nkM+xqKoc
yHTtAgUyflpenbrr4pRZf783XgI0bn/FhoMFQtAvSblru3NfEFQUtKIY82+Xa5H5
g+cezSDYLwKBgBeViB39GJ6vC16azzZ6XhmX95gl5HDUrMFBVKzqyhiupf1w64HF
G+FZhP97BZO/Bt91nomg1FgUiMqVJkAF6cjtQ7YqVCHBtO0bLlA8iWNsQx31Spsj
jIL6+NuIZL0i8tjoH2N8euVVH5mVNmiLnHGeicflZM4HHrm3BWHrlTQBAoGBALeW
W98CQFe8Pw542ixDiESOR8fz6UwrXWAb/pwTxL20oKV8GUxJNFhtKJK3CEMZ2JB7
uWoEqYairvUTWOxSVeBQPPwSAWcNeE6f+0mKMGa1EQNIRDDLq3fOcNYevkOPKB7g
kZQtQzclCAvGYQ8aJL6MmvY3DWOVx2YuD4+COE6BAoGAEGdChfJW5QGXaXEO/PnA
PbQCCzcqbs+0O6LVR1w68H0WQww94tZjfWPqn9kvwjzLd22ZMmdiBJ3bEbDeCjmG
Ybt48kS7y9n22CDgL7JkatszYpybvBSrDQL7ms7x2kKPkTMb7C5zpIIzdtvwH+Jf
6K3kQbqfFCM7VmyR7AmoyOk=
-----END PRIVATE KEY-----`

const encryptionService = new RsaEncryptionService({ publicKey, privateKey })

describe('RsaEncryptionService', () => {
it('should encrypt and decrypt data correctly', () => {
const data = 'Hello, World!'
const encryptedData = encryptionService.encrypt(data)
const decryptedData = encryptionService.decrypt(encryptedData)

expect(decryptedData).toBe(data)
})

it('should throw an error when decrypting with incorrect data', () => {
const incorrectData = 'invalidEncryptedData'

expect(() => {
encryptionService.decrypt(incorrectData)
}).toThrow()
})

it('should throw an error when encrypting with an invalid public key', () => {
const invalidPublicKey = 'invalidPublicKey'
const invalidEncryptionService = new RsaEncryptionService({ publicKey: invalidPublicKey, privateKey })

expect(() => {
invalidEncryptionService.encrypt('test')
}).toThrow()
})

it('should throw an error when decrypting with an invalid private key', () => {
const data = 'Hello, World!'
const encryptedData = encryptionService.encrypt(data)
const invalidPrivateKey = 'invalidPrivateKey'
const invalidEncryptionService = new RsaEncryptionService({ publicKey, privateKey: invalidPrivateKey })

expect(() => {
invalidEncryptionService.decrypt(encryptedData)
}).toThrow()
})
})
115 changes: 89 additions & 26 deletions __test__/projects/GitHubProjectDataSource.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,29 @@
import { GitHubProjectDataSource } from "@/features/projects/data"
import RemoteConfig from "@/features/projects/domain/RemoteConfig"

/**
* Simple encryption service for testing. Does nothing.
*/
const noopEncryptionService = {
encrypt: function (data: string): string {
return data
},
decrypt: function (encryptedDataBase64: string): string {
return encryptedDataBase64
}
}

/**
* Simple encoder for testing
*/
const base64RemoteConfigEncoder = {
encode: function (remoteConfig: RemoteConfig): string {
return Buffer.from(JSON.stringify(remoteConfig)).toString("base64")
},
decode: function (encodedString: string): RemoteConfig {
return JSON.parse(Buffer.from(encodedString, "base64").toString())
}
}

test("It loads repositories from data source", async () => {
let didLoadRepositories = false
Expand All @@ -9,7 +34,9 @@ test("It loads repositories from data source", async () => {
didLoadRepositories = true
return []
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
await sut.getProjects()
expect(didLoadRepositories).toBeTruthy()
Expand Down Expand Up @@ -43,7 +70,9 @@ test("It maps projects including branches and tags", async () => {
}]
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects).toEqual([{
Expand Down Expand Up @@ -107,7 +136,9 @@ test("It removes suffix from project name", async () => {
}]
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].id).toEqual("acme-foo")
Expand Down Expand Up @@ -147,7 +178,9 @@ test("It supports multiple OpenAPI specifications on a branch", async () => {
}]
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects).toEqual([{
Expand Down Expand Up @@ -209,7 +242,9 @@ test("It filters away projects with no versions", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects.length).toEqual(0)
Expand Down Expand Up @@ -243,7 +278,9 @@ test("It filters away branches with no specifications", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].versions.length).toEqual(1)
Expand Down Expand Up @@ -283,7 +320,9 @@ test("It filters away tags with no specifications", async () => {
}]
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].versions.length).toEqual(2)
Expand Down Expand Up @@ -314,7 +353,9 @@ test("It reads image from configuration file with .yml extension", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].imageURL).toEqual("/api/blob/acme/foo-openapi/icon.png?ref=12345678")
Expand Down Expand Up @@ -345,7 +386,9 @@ test("It reads display name from configuration file with .yml extension", async
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].id).toEqual("acme-foo")
Expand Down Expand Up @@ -378,7 +421,9 @@ test("It reads image from configuration file with .yaml extension", async () =>
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].imageURL).toEqual("/api/blob/acme/foo-openapi/icon.png?ref=12345678")
Expand Down Expand Up @@ -409,7 +454,9 @@ test("It reads display name from configuration file with .yaml extension", async
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].id).toEqual("acme-foo")
Expand Down Expand Up @@ -478,7 +525,9 @@ test("It sorts projects alphabetically", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].name).toEqual("anne")
Expand Down Expand Up @@ -529,7 +578,9 @@ test("It sorts versions alphabetically", async () => {
}]
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].versions[0].name).toEqual("1.0")
Expand Down Expand Up @@ -593,7 +644,9 @@ test("It prioritizes main, master, develop, and development branch names when so
}]
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].versions[0].name).toEqual("main")
Expand Down Expand Up @@ -641,7 +694,9 @@ test("It identifies the default branch in returned versions", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
const defaultVersionNames = projects[0]
Expand Down Expand Up @@ -682,7 +737,9 @@ test("It adds remote versions from the project configuration", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].versions).toEqual([{
Expand All @@ -692,11 +749,11 @@ test("It adds remote versions from the project configuration", async () => {
specifications: [{
id: "huey",
name: "Huey",
url: `/api/proxy?url=${encodeURIComponent("https://example.com/huey.yml")}`
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/huey.yml" })}`
}, {
id: "dewey",
name: "Dewey",
url: `/api/proxy?url=${encodeURIComponent("https://example.com/dewey.yml")}`
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/dewey.yml" })}`
}]
}, {
id: "bobby",
Expand All @@ -705,7 +762,7 @@ test("It adds remote versions from the project configuration", async () => {
specifications: [{
id: "louie",
name: "Louie",
url: `/api/proxy?url=${encodeURIComponent("https://example.com/louie.yml")}`
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/louie.yml" })}`
}]
}])
})
Expand Down Expand Up @@ -745,7 +802,9 @@ test("It modifies ID of remote version if the ID already exists", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].versions).toEqual([{
Expand All @@ -766,7 +825,7 @@ test("It modifies ID of remote version if the ID already exists", async () => {
specifications: [{
id: "baz",
name: "Baz",
url: `/api/proxy?url=${encodeURIComponent("https://example.com/baz.yml")}`
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
}]
}, {
id: "bar2",
Expand All @@ -775,7 +834,7 @@ test("It modifies ID of remote version if the ID already exists", async () => {
specifications: [{
id: "hello",
name: "Hello",
url: `/api/proxy?url=${encodeURIComponent("https://example.com/hello.yml")}`
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/hello.yml" })}`
}]
}])
})
Expand Down Expand Up @@ -806,7 +865,9 @@ test("It lets users specify the ID of a remote version", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].versions).toEqual([{
Expand All @@ -816,7 +877,7 @@ test("It lets users specify the ID of a remote version", async () => {
specifications: [{
id: "baz",
name: "Baz",
url: `/api/proxy?url=${encodeURIComponent("https://example.com/baz.yml")}`
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
}]
}])
})
Expand Down Expand Up @@ -847,7 +908,9 @@ test("It lets users specify the ID of a remote specification", async () => {
tags: []
}]
}
}
},
encryptionService: noopEncryptionService,
remoteConfigEncoder: base64RemoteConfigEncoder
})
const projects = await sut.getProjects()
expect(projects[0].versions).toEqual([{
Expand All @@ -857,7 +920,7 @@ test("It lets users specify the ID of a remote specification", async () => {
specifications: [{
id: "some-spec",
name: "Baz",
url: `/api/proxy?url=${encodeURIComponent("https://example.com/baz.yml")}`
url: `/api/remotes/${base64RemoteConfigEncoder.encode({ url: "https://example.com/baz.yml" })}`
}]
}])
})
Loading

0 comments on commit ec4af52

Please sign in to comment.