Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic auth via config file #451

Merged
merged 35 commits into from
Dec 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
61d4f8c
First draft of auth via remotes endpoint
ulrikandersen Dec 4, 2024
ef793d5
Introduce encryption service
ulrikandersen Dec 4, 2024
82869a1
Add page for encrypting with public key
ulrikandersen Dec 4, 2024
50b3652
Handle auth for remote specifications
ulrikandersen Dec 4, 2024
75ea62d
Base64 encode remote spec config
ulrikandersen Dec 4, 2024
d3b962f
Set filename - used when browser downloads the file
ulrikandersen Dec 4, 2024
d422deb
Bye bye proxy - use remotes endpoint now
ulrikandersen Dec 4, 2024
a57bc3c
Export IEncryptionService
ulrikandersen Dec 4, 2024
23644ba
Inject encryption service into GitHubProjectDataSource
ulrikandersen Dec 4, 2024
6919703
Move types to separate files
ulrikandersen Dec 4, 2024
d6510d5
Encapsulate encoding/decoding of RemoteConfig
ulrikandersen Dec 4, 2024
06ab813
Remove comment
ulrikandersen Dec 4, 2024
0a0453b
Export encryption service
ulrikandersen Dec 5, 2024
4edfe94
Create nicer layout for encryption page
ulrikandersen Dec 5, 2024
5884fda
Move encrypt to (home) folder
ulrikandersen Dec 5, 2024
cedc18c
Move encryption service
ulrikandersen Dec 5, 2024
5e03f97
Fix headline
ulrikandersen Dec 5, 2024
ec15451
Introduce interface for RemoteConfigEncoder
ulrikandersen Dec 5, 2024
e8c300a
Adjust GitHubProjectDataSource tests
ulrikandersen Dec 5, 2024
cc4f3db
Merge branch 'develop' into basic-auth
simonbs Dec 5, 2024
88c20ad
Adjust text on encrypt page
ulrikandersen Dec 5, 2024
dd7f0c7
Add test for encryption service
ulrikandersen Dec 5, 2024
6014aee
Add test for remote config encoder
ulrikandersen Dec 5, 2024
e44a49b
Expect "Unexpected token"
ulrikandersen Dec 5, 2024
f94e45c
Updates texts
simonbs Dec 5, 2024
b481ff2
Styles text field with encrypted value
simonbs Dec 5, 2024
3ea4762
Merge branch 'basic-auth' of github.com:shapehq/framna-docs into basi…
simonbs Dec 5, 2024
534e88b
Aligns indentation
simonbs Dec 5, 2024
b767296
Hardcode keypair in test
ulrikandersen Dec 5, 2024
ad394d4
Remove unused AccountCircle
ulrikandersen Dec 5, 2024
beaac0b
Removes unused import
simonbs Dec 5, 2024
66f7eac
Ignores unused argument
simonbs Dec 5, 2024
7a36503
Errors when URL includes basic auth
simonbs Dec 5, 2024
961fd51
Merge branch 'basic-auth' of github.com:shapehq/framna-docs into basi…
simonbs Dec 5, 2024
7092c9c
Fixes linting error
simonbs Dec 5, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading