Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ac4d12b
Adds GuestAccessTokenRepository
simonbs Nov 9, 2023
8d90a42
Moves RepositoryRestrictingAccessTokenDataSource
simonbs Nov 9, 2023
324158b
Moves CachingRepositoryAccessReaderConfig
simonbs Nov 9, 2023
03c7b9b
Adds tests for GuestAccessTokenRepository
simonbs Nov 9, 2023
ed58497
Replaces setExpiring with set
simonbs Nov 9, 2023
b66e0bc
Adds missing delete function
simonbs Nov 9, 2023
e59531b
Fixes unit tests
simonbs Nov 9, 2023
a0f60f1
Fixes typo in export name
simonbs Nov 9, 2023
f83951e
Introduces session validity check to show informative errors
simonbs Nov 9, 2023
e7ebe2a
Adds tests for HostOnlySessionValidator
simonbs Nov 10, 2023
5aaef03
Adds tests for mergeSessionValidity
simonbs Nov 10, 2023
d31f4d5
Merge branch 'develop' into enhancement/guestaccesstokenrepository
simonbs Nov 10, 2023
43fb977
Merge pull request #144 from shapehq/enhancement/guestaccesstokenrepo…
simonbs Nov 10, 2023
ec3e3f9
Merge branch 'develop' into enhancement/session-errors
simonbs Nov 10, 2023
45d6d86
Fixes linting warnings
simonbs Nov 10, 2023
78c68f0
Fixes naming of blockingSessionValidator
simonbs Nov 10, 2023
fb337b1
Fixes filename
simonbs Nov 10, 2023
4ed7f6c
Merge pull request #145 from shapehq/enhancement/session-errors
simonbs Nov 10, 2023
f76f348
Cancels creating access token when repository list empty
simonbs Nov 10, 2023
8e8143c
Specializes error when repository list is empty
simonbs Nov 10, 2023
3817d8b
Merge branch 'develop' into bugfix/no-roles
simonbs Nov 10, 2023
3725166
Merge pull request #146 from shapehq/bugfix/no-roles
simonbs Nov 10, 2023
82c98c0
Improves wording of error message
simonbs Nov 10, 2023
0e43080
Fixes incorrect wording
simonbs Nov 10, 2023
a906a4f
Merge pull request #147 from shapehq/enhancement/error-message-wording
simonbs Nov 10, 2023
530f5fc
Merge branch 'main' into develop
simonbs Nov 10, 2023
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CachingRepositoryAccessReaderConfig } from "../../src/features/auth/domain"
import { CachingRepositoryAccessReader } from "../../src/features/auth/domain"

test("It fetches repository names for user if they are not cached", async () => {
let didFetchRepositoryNames = false
let requestedUserId: string | undefined
const sut = new CachingRepositoryAccessReaderConfig({
const sut = new CachingRepositoryAccessReader({
repository: {
async get() {
return null
Expand All @@ -27,7 +27,7 @@ test("It fetches repository names for user if they are not cached", async () =>

test("It does not fetch repository names if they are cached", async () => {
let didFetchRepositoryNames = false
const sut = new CachingRepositoryAccessReaderConfig({
const sut = new CachingRepositoryAccessReader({
repository: {
async get() {
return "[\"foo\"]"
Expand All @@ -50,16 +50,16 @@ test("It does not fetch repository names if they are cached", async () => {
test("It caches fetched repository names for user", async () => {
let cachedUserId: string | undefined
let cachedRepositoryNames: string | undefined
const sut = new CachingRepositoryAccessReaderConfig({
const sut = new CachingRepositoryAccessReader({
repository: {
async get() {
return null
},
async set(userId, value) {
async set() {},
async setExpiring(userId: string, value: string) {
cachedUserId = userId
cachedRepositoryNames = value
},
async setExpiring() {},
async delete() {}
},
repositoryAccessReader: {
Expand All @@ -74,7 +74,7 @@ test("It caches fetched repository names for user", async () => {
})

test("It decodes cached repository names", async () => {
const sut = new CachingRepositoryAccessReaderConfig({
const sut = new CachingRepositoryAccessReader({
repository: {
async get() {
return "[\"foo\",\"bar\"]"
Expand Down
83 changes: 83 additions & 0 deletions __test__/auth/GitHubOrganizationSessionValidator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
GitHubOrganizationSessionValidator,
SessionValidity
} from "../../src/features/auth/domain"

test("It requests organization membership status for the specified organization", async () => {
let queriedOrganizationName: string | undefined
const sut = new GitHubOrganizationSessionValidator({
acceptedOrganization: "foo",
organizationMembershipStatusReader: {
async getOrganizationMembershipStatus(request) {
queriedOrganizationName = request.organizationName
return { state: "active" }
}
}
})
await sut.validateSession()
expect(queriedOrganizationName).toBe("foo")
})

test("It considers session valid when membership state is \"active\"", async () => {
const sut = new GitHubOrganizationSessionValidator({
acceptedOrganization: "foo",
organizationMembershipStatusReader: {
async getOrganizationMembershipStatus() {
return { state: "active" }
}
}
})
const sessionValidity = await sut.validateSession()
expect(sessionValidity).toEqual(SessionValidity.VALID)
})

test("It considers user not to be part of the organization when membership state is \"pending\"", async () => {
const sut = new GitHubOrganizationSessionValidator({
acceptedOrganization: "foo",
organizationMembershipStatusReader: {
async getOrganizationMembershipStatus() {
return { state: "pending" }
}
}
})
const sessionValidity = await sut.validateSession()
expect(sessionValidity).toEqual(SessionValidity.OUTSIDE_GITHUB_ORGANIZATION)
})

test("It considers user not to be part of the organization when receiving HTTP 404", async () => {
const sut = new GitHubOrganizationSessionValidator({
acceptedOrganization: "foo",
organizationMembershipStatusReader: {
async getOrganizationMembershipStatus() {
throw { status: 404, message: "User is not member of organization"}
}
}
})
const sessionValidity = await sut.validateSession()
expect(sessionValidity).toEqual(SessionValidity.OUTSIDE_GITHUB_ORGANIZATION)
})

test("It considers organization to have blocked the GitHub app when receiving HTTP 403", async () => {
const sut = new GitHubOrganizationSessionValidator({
acceptedOrganization: "foo",
organizationMembershipStatusReader: {
async getOrganizationMembershipStatus() {
throw { status: 403, message: "Organization has blocked GitHub app"}
}
}
})
const sessionValidity = await sut.validateSession()
expect(sessionValidity).toEqual(SessionValidity.GITHUB_APP_BLOCKED)
})

test("It forwards error when getting membership status throws unknown error", async () => {
const sut = new GitHubOrganizationSessionValidator({
acceptedOrganization: "foo",
organizationMembershipStatusReader: {
async getOrganizationMembershipStatus() {
throw { status: 500 }
}
}
})
await expect(sut.validateSession()).rejects.toEqual({ status: 500 })
})
52 changes: 52 additions & 0 deletions __test__/auth/GuestAccessTokenRepository.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { GuestAccessTokenRepository } from "../../src/features/auth/domain"

test("It reads access token for user", async () => {
let readUserId: string | undefined
const sut = new GuestAccessTokenRepository({
async get(userId) {
readUserId = userId
return "foo"
},
async setExpiring() {},
async delete() {}
})
const accessToken = await sut.get("1234")
expect(readUserId).toBe("1234")
expect(accessToken).toBe("foo")
})

test("It stores access token for user", async () => {
let storedUserId: string | undefined
let storedToken: string | undefined
let storedTimeToLive: number | undefined
const sut = new GuestAccessTokenRepository({
async get() {
return "foo"
},
async setExpiring(userId, token, timeToLive) {
storedUserId = userId
storedToken = token
storedTimeToLive = timeToLive
},
async delete(userId) {}
})
await sut.set("1234", "bar")
expect(storedUserId).toBe("1234")
expect(storedToken).toBe("bar")
expect(storedTimeToLive).toBeGreaterThan(0)
})

test("It deletes access token for user", async () => {
let deletedUserId: string | undefined
const sut = new GuestAccessTokenRepository({
async get() {
return "foo"
},
async setExpiring() {},
async delete(userId) {
deletedUserId = userId
}
})
await sut.delete("1234")
expect(deletedUserId).toBe("1234")
})
27 changes: 1 addition & 26 deletions __test__/auth/GuestAccessTokenService.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ test("It gets the access token for the user", async () => {
readUserId = userId
return "foo"
},
async setExpiring() {}
async set() {}
},
dataSource: {
async getAccessToken() {
Expand All @@ -25,28 +25,3 @@ test("It gets the access token for the user", async () => {
expect(readUserId).toBe("1234")
expect(accessToken).toBe("foo")
})

test("It refreshes access token on demand when there is no cached access token", async () => {
let didRefreshAccessToken = false
const sut = new GuestAccessTokenService({
userIdReader: {
async getUserId() {
return "1234"
}
},
repository: {
async get() {
return null
},
async setExpiring() {}
},
dataSource: {
async getAccessToken() {
didRefreshAccessToken = true
return "foo"
}
}
})
await sut.getAccessToken()
expect(didRefreshAccessToken).toBeTruthy()
})
43 changes: 43 additions & 0 deletions __test__/auth/HostOnlySessionValidator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
HostOnlySessionValidator,
SessionValidity
} from "../../src/features/auth/domain"

test("It validates session when user is host", async () => {
let didValidateSession = false
const sut = new HostOnlySessionValidator({
isGuestReader: {
async getIsGuest() {
return false
}
},
sessionValidator: {
async validateSession() {
didValidateSession = true
return SessionValidity.VALID
},
}
})
await sut.validateSession()
expect(didValidateSession).toBeTruthy()
})


test("It does not validate session when user is guest", async () => {
let didValidateSession = false
const sut = new HostOnlySessionValidator({
isGuestReader: {
async getIsGuest() {
return true
}
},
sessionValidator: {
async validateSession() {
didValidateSession = true
return SessionValidity.VALID
},
}
})
await sut.validateSession()
expect(didValidateSession).toBeFalsy()
})
28 changes: 28 additions & 0 deletions __test__/auth/SessionValidity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {
mergeSessionValidity,
SessionValidity
} from "../../src/features/auth/domain"

test("It returns invalid validity when left-hand side validity indicates that the session is invalid", async () => {
const sut = mergeSessionValidity(
SessionValidity.INVALID_ACCESS_TOKEN,
SessionValidity.VALID
)
expect(sut).toEqual(SessionValidity.INVALID_ACCESS_TOKEN)
})

test("It returns invalid validity when right-hand side validity indicates that the session is invalid", async () => {
const sut = mergeSessionValidity(
SessionValidity.VALID,
SessionValidity.INVALID_ACCESS_TOKEN
)
expect(sut).toEqual(SessionValidity.INVALID_ACCESS_TOKEN)
})

test("It returns valid validity when both validities indicate that the session is valid", async () => {
const sut = mergeSessionValidity(
SessionValidity.VALID,
SessionValidity.VALID
)
expect(sut).toEqual(SessionValidity.VALID)
})
Loading