Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
a637d60
Adds concept of guest users
simonbs Nov 7, 2023
0a0b6eb
Separates access token and credentials transferring
simonbs Nov 7, 2023
c1df85a
Ensures guest sessions are always valid
simonbs Nov 7, 2023
fcd481d
Replaces SessionOAuthTokenBarrier with SessionAccessTokenBarrier
simonbs Nov 7, 2023
f08d6ad
Adds tests for SessionValidator
simonbs Nov 7, 2023
1f30916
Fixes object incorrectly passed
simonbs Nov 7, 2023
0f1966f
Fixes credentials not transferred immediately
simonbs Nov 7, 2023
ab8a27a
Adds tests for IsUserGuestReader
simonbs Nov 7, 2023
8b4ff43
Adds tests for CachingUserIdentityProviderReader
simonbs Nov 7, 2023
dd7ead4
Merge branch 'bugfix/authenticating-webhooks' into enhancement/guest-…
simonbs Nov 7, 2023
55857bc
Adds missing await when transferring credentials
simonbs Nov 7, 2023
c175349
Creates guest access tokens with limited repository access
simonbs Nov 7, 2023
67d5996
Merge branch 'develop' into enhancement/guest-login
simonbs Nov 7, 2023
8778d03
Adds CompositeLogInHandler
simonbs Nov 7, 2023
f0f894f
Resets has_pending_invitation flag
simonbs Nov 7, 2023
df9fa79
Logs callback error
simonbs Nov 7, 2023
553a421
Deletes access token
simonbs Nov 7, 2023
f4ad190
Improves error handling
simonbs Nov 7, 2023
96b8305
Fetch guest access tokens on demand
simonbs Nov 7, 2023
a0d7961
Removes GuestCredentialsTransferrer
simonbs Nov 7, 2023
87e4580
Only validates access token for host users
simonbs Nov 7, 2023
8b2911a
Returns no projects when access token does not exist
simonbs Nov 7, 2023
8093c89
Simplifies imports
simonbs Nov 7, 2023
92f9f70
Removes irrelevant tests
simonbs Nov 7, 2023
231dac4
F
simonbs Nov 7, 2023
fa19fc0
Fixes linting warnings
simonbs Nov 7, 2023
52a7950
Improves imports
simonbs Nov 7, 2023
68c4b26
Merge branch 'enhancement/improves-imports' into enhancement/guest-login
simonbs Nov 7, 2023
677e7fc
Merge branch 'enhancement/improves-imports' into enhancement/guest-login
simonbs Nov 7, 2023
a5df596
Merge branch 'develop' into enhancement/guest-login
simonbs Nov 7, 2023
29f9144
Hides edit button when logged in as guest
simonbs Nov 7, 2023
b04aaa6
Disables link to repository ofr guests
simonbs Nov 7, 2023
0a6dbc4
Fixes margin
simonbs Nov 7, 2023
bd0aded
Merge branch 'develop' into enhancement/guest-login
simonbs Nov 7, 2023
8986a8e
Renames SessionAccessTokenService to AccessTokenService
simonbs Nov 8, 2023
6da7aae
Merge branch 'develop' into enhancement/guest-login
simonbs Nov 8, 2023
3d53142
Clean up imports
simonbs Nov 8, 2023
9436fb9
Logs authentication errors
simonbs Nov 8, 2023
5db7122
Merge branch 'develop' into enhancement/log-auth-errors
simonbs Nov 8, 2023
66c1e0c
Merge branch 'develop' into enhancement/clean-up-imports
simonbs Nov 8, 2023
55042af
Merge pull request #137 from shapehq/enhancement/clean-up-imports
simonbs Nov 8, 2023
29fb9ea
Merge branch 'develop' into enhancement/log-auth-errors
simonbs Nov 8, 2023
e68ad43
Merge pull request #138 from shapehq/enhancement/log-auth-errors
simonbs Nov 8, 2023
4225bb3
Merge branch 'develop' into enhancement/guest-login
simonbs Nov 8, 2023
35eecdf
Merge branch 'enhancement/guest-login' of github.com:shapehq/shape-do…
simonbs Nov 8, 2023
11d6c1f
Removes logic resetting has_pending_invitation
simonbs Nov 8, 2023
53e8043
Revert "Removes logic resetting has_pending_invitation"
simonbs Nov 8, 2023
53b5d93
Adds missing export
simonbs Nov 8, 2023
dc02280
Adds missing trailing whitespace
simonbs Nov 8, 2023
ec56464
Cleans up comment
simonbs Nov 8, 2023
46af186
Removes forgiving data source
simonbs Nov 8, 2023
19b80da
Revert "Removes forgiving data source"
simonbs Nov 8, 2023
fc797b8
Proxies all blob requests
simonbs Nov 8, 2023
47d8a9d
Moves /api/github/blob to /api/blob
simonbs Nov 8, 2023
4e59add
Merge pull request #141 from shapehq/enhancement/proxy-blob-requests
simonbs Nov 8, 2023
d5dd81e
Merge branch 'develop' into enhancement/guest-login
simonbs Nov 8, 2023
ef88ba0
Update invite-guest.yml
simonbs Nov 8, 2023
aec64eb
Merge pull request #131 from shapehq/enhancement/guest-login
simonbs Nov 8, 2023
e69f3c6
Merge branch 'develop' into enhancement/hides-edit-button-for-guests
simonbs Nov 8, 2023
4aa5e7f
Merge branch 'develop' into enhancement/guest-user-access-token-unava…
simonbs Nov 8, 2023
207cd13
Merge pull request #140 from shapehq/enhancement/guest-user-access-to…
simonbs Nov 8, 2023
d6a5a3e
Merge branch 'develop' into enhancement/resets-has-pending-invitation…
simonbs Nov 8, 2023
1aa5df0
Merge pull request #139 from shapehq/enhancement/resets-has-pending-i…
simonbs Nov 8, 2023
f8a94b9
Merge branch 'develop' into enhancement/hides-edit-button-for-guests
simonbs Nov 8, 2023
6ed664f
Merge pull request #133 from shapehq/enhancement/hides-edit-button-fo…
simonbs Nov 8, 2023
f61b67d
Expires values cached in Redis
simonbs Nov 8, 2023
e82b6ea
Merge pull request #142 from shapehq/enhancement/expire-cached-values
simonbs Nov 8, 2023
9ebf40a
Merge branch 'main' into develop
simonbs Nov 9, 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
2 changes: 1 addition & 1 deletion .github/workflows/invite-guest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
description: E-mail address to send invitation to
required: true
roles:
description: Comma-separated list of roles
description: Comma-separated list of repositories user needs access to
required: true
env:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN_SHAPE_DOCS }}
Expand Down
141 changes: 88 additions & 53 deletions __test__/auth/AccessTokenService.test.ts
Original file line number Diff line number Diff line change
@@ -1,86 +1,121 @@
import { AccessTokenService } from "../../src/features/auth/domain"
import { OAuthToken } from "../../src/features/auth/domain"

test("It gets the access token for the user", async () => {
let readUserID: string | undefined
test("It reads the access token for a guest user", async () => {
let didReadAccessToken = false
const sut = new AccessTokenService({
userIdReader: {
async getUserId() {
return "1234"
isGuestReader: {
async getIsGuest() {
return true
}
},
repository: {
async get(userId) {
readUserID = userId
return { accessToken: "foo", refreshToken: "bar" }
guestAccessTokenService: {
async getAccessToken() {
didReadAccessToken = true
return "oldAccessToken"
},
async set() {},
async delete() {},
async refreshAccessToken() {
return "newAccessToken"
}
},
refresher: {
async refreshOAuthToken() {
return { accessToken: "foo", refreshToken: "bar" }
hostAccessTokenService: {
async getAccessToken() {
return "oldAccessToken"
},
async refreshAccessToken() {
return "newAccessToken"
}
}
})
const accessToken = await sut.getAccessToken()
expect(readUserID).toBe("1234")
expect(accessToken).toBe("foo")
await sut.getAccessToken()
expect(didReadAccessToken).toBeTruthy()
})

test("It refreshes OAuth using stored refresh token", async () => {
let usedRefreshToken: string | undefined
test("It refreshes the access token for a guest user", async () => {
let didRefreshAccessToken = false
const sut = new AccessTokenService({
userIdReader: {
async getUserId() {
return "1234"
isGuestReader: {
async getIsGuest() {
return true
}
},
repository: {
async get() {
return { accessToken: "oldAccessToken", refreshToken: "oldRefreshToken" }
guestAccessTokenService: {
async getAccessToken() {
return "oldAccessToken"
},
async set() {},
async delete() {},
async refreshAccessToken() {
didRefreshAccessToken = true
return "newAccessToken"
}
},
refresher: {
async refreshOAuthToken(refreshToken) {
usedRefreshToken = refreshToken
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
hostAccessTokenService: {
async getAccessToken() {
return "oldAccessToken"
},
async refreshAccessToken() {
return "newAccessToken"
}
}
})
await sut.refreshAccessToken("oldAccessToken")
expect(usedRefreshToken).toBe("oldRefreshToken")
expect(didRefreshAccessToken).toBeTruthy()
})

test("It stores the new OAuth token for the user", async () => {
let storedUserId: string | undefined
let storedOAuthToken: OAuthToken | undefined
test("It reads the access token for a host user", async () => {
let didReadAccessToken = false
const sut = new AccessTokenService({
userIdReader: {
async getUserId() {
return "1234"
isGuestReader: {
async getIsGuest() {
return false
}
},
repository: {
async get() {
return { accessToken: "oldAccessToken", refreshToken: "oldRefreshToken" }
guestAccessTokenService: {
async getAccessToken() {
return "oldAccessToken"
},
async set(userId, oAuthToken) {
storedUserId = userId
storedOAuthToken = oAuthToken
async refreshAccessToken() {
return "newAccessToken"
}
},
hostAccessTokenService: {
async getAccessToken() {
didReadAccessToken = true
return "oldAccessToken"
},
async refreshAccessToken() {
return "newAccessToken"
}
}
})
await sut.getAccessToken()
expect(didReadAccessToken).toBeTruthy()
})

test("It refreshes the access token for a host user", async () => {
let didRefreshAccessToken = false
const sut = new AccessTokenService({
isGuestReader: {
async getIsGuest() {
return false
}
},
guestAccessTokenService: {
async getAccessToken() {
return "oldAccessToken"
},
async delete() {},
async refreshAccessToken() {
return "newAccessToken"
}
},
refresher: {
async refreshOAuthToken() {
return { accessToken: "newAccessToken", refreshToken: "newRefreshToken" }
hostAccessTokenService: {
async getAccessToken() {
return "oldAccessToken"
},
async refreshAccessToken() {
didRefreshAccessToken = true
return "newAccessToken"
}
}
})
await sut.refreshAccessToken("foo")
expect(storedUserId).toBe("1234")
expect(storedOAuthToken?.accessToken).toBe("newAccessToken")
expect(storedOAuthToken?.refreshToken).toBe("newRefreshToken")
await sut.refreshAccessToken("oldAccessToken")
expect(didRefreshAccessToken).toBeTruthy()
})
94 changes: 94 additions & 0 deletions __test__/auth/CachingRepositoryAccessReaderConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { CachingRepositoryAccessReaderConfig } 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({
repository: {
async get() {
return null
},
async set() {},
async setExpiring() {},
async delete() {}
},
repositoryAccessReader: {
async getRepositoryNames(userId: string) {
didFetchRepositoryNames = true
requestedUserId = userId
return []
}
}
})
await sut.getRepositoryNames("1234")
expect(didFetchRepositoryNames).toBeTruthy()
expect(requestedUserId).toEqual("1234")
})

test("It does not fetch repository names if they are cached", async () => {
let didFetchRepositoryNames = false
const sut = new CachingRepositoryAccessReaderConfig({
repository: {
async get() {
return "[\"foo\"]"
},
async set() {},
async setExpiring() {},
async delete() {}
},
repositoryAccessReader: {
async getRepositoryNames() {
didFetchRepositoryNames = true
return []
}
}
})
await sut.getRepositoryNames("1234")
expect(didFetchRepositoryNames).toBeFalsy()
})

test("It caches fetched repository names for user", async () => {
let cachedUserId: string | undefined
let cachedRepositoryNames: string | undefined
const sut = new CachingRepositoryAccessReaderConfig({
repository: {
async get() {
return null
},
async set(userId, value) {
cachedUserId = userId
cachedRepositoryNames = value
},
async setExpiring() {},
async delete() {}
},
repositoryAccessReader: {
async getRepositoryNames() {
return ["foo"]
}
}
})
await sut.getRepositoryNames("1234")
expect(cachedUserId).toEqual("1234")
expect(cachedRepositoryNames).toEqual("[\"foo\"]")
})

test("It decodes cached repository names", async () => {
const sut = new CachingRepositoryAccessReaderConfig({
repository: {
async get() {
return "[\"foo\",\"bar\"]"
},
async set() {},
async setExpiring() {},
async delete() {}
},
repositoryAccessReader: {
async getRepositoryNames() {
return []
}
}
})
const repositoryNames = await sut.getRepositoryNames("1234")
expect(repositoryNames).toEqual(["foo", "bar"])
})
63 changes: 63 additions & 0 deletions __test__/auth/CachingUserIdentityProviderReader.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { CachingUserIdentityProviderReader } from "../../src/features/auth/domain"
import { UserIdentityProvider } from "../../src/features/auth/domain"

test("It fetches user identity provider if it is not cached", async () => {
let didFetchUserIdentityProvider = false
const sut = new CachingUserIdentityProviderReader({
async get() {
return null
},
async set() {},
async setExpiring() {},
async delete() {}
}, {
async getUserIdentityProvider() {
didFetchUserIdentityProvider = true
return UserIdentityProvider.GITHUB
},
})
await sut.getUserIdentityProvider("foo")
expect(didFetchUserIdentityProvider).toBeTruthy()
})

test("It does not fetch user identity provider if it is cached", async () => {
let didFetchUserIdentityProvider = false
const sut = new CachingUserIdentityProviderReader({
async get() {
return UserIdentityProvider.GITHUB
},
async set() {},
async setExpiring() {},
async delete() {}
}, {
async getUserIdentityProvider() {
didFetchUserIdentityProvider = true
return UserIdentityProvider.GITHUB
},
})
await sut.getUserIdentityProvider("foo")
expect(didFetchUserIdentityProvider).toBeFalsy()
})

test("It caches fetched user identity provider for user", async () => {
let cachedUserId: string | undefined
let cachedUserIdentityProvider: string | undefined
const sut = new CachingUserIdentityProviderReader({
async get() {
return null
},
async set() {},
async setExpiring(userId, userIdentityProvider) {
cachedUserId = userId
cachedUserIdentityProvider = userIdentityProvider
},
async delete() {}
}, {
async getUserIdentityProvider() {
return UserIdentityProvider.USERNAME_PASSWORD
},
})
await sut.getUserIdentityProvider("1234")
expect(cachedUserId).toBe("1234")
expect(cachedUserIdentityProvider).toBe(UserIdentityProvider.USERNAME_PASSWORD.toString())
})
24 changes: 24 additions & 0 deletions __test__/auth/CompositeLogInHandler.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { CompositeLogInHandler } from "../../src/features/auth/domain"

test("It invokes all log in handlers for user", async () => {
let userId1: string | undefined
let userId2: string | undefined
let userId3: string | undefined
const sut = new CompositeLogInHandler([{
async handleLogIn(userId) {
userId1 = userId
}
}, {
async handleLogIn(userId) {
userId2 = userId
}
}, {
async handleLogIn(userId) {
userId3 = userId
}
}])
await sut.handleLogIn("1234")
expect(userId1).toEqual("1234")
expect(userId2).toEqual("1234")
expect(userId3).toEqual("1234")
})
Loading