-
-
Notifications
You must be signed in to change notification settings - Fork 394
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(console): update user access immediately on tenant role updates (#…
…5720) * feat(console): update user access immediately on tenant role updates * chore: improve comments Co-authored-by: Gao Sun <gao@silverhand.io> --------- Co-authored-by: Gao Sun <gao@silverhand.io>
- Loading branch information
1 parent
75deb2d
commit 59acede
Showing
19 changed files
with
164 additions
and
100 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,6 +48,6 @@ | |
"access": "public" | ||
}, | ||
"devDependencies": { | ||
"@logto/cloud": "0.2.5-ab8a489" | ||
"@logto/cloud": "0.2.5-821690c" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
import { Prompt, useLogto } from '@logto/react'; | ||
import { getTenantOrganizationId } from '@logto/schemas'; | ||
import { useContext, useEffect, useState } from 'react'; | ||
|
||
import { TenantsContext } from '@/contexts/TenantsProvider'; | ||
import useCurrentTenantScopes from '@/hooks/use-current-tenant-scopes'; | ||
import useRedirectUri from '@/hooks/use-redirect-uri'; | ||
import { saveRedirect } from '@/utils/storage'; | ||
|
||
/** | ||
* Listens to the tenant scope changes for the current signed-in user. This hook will fetch the tenant scopes | ||
* for the user, and compare it with the "scope" token claim in access token. After comparing the scopes: | ||
* | ||
* - If the user has been granted new scopes, it will re-consent to obtain the additional scopes. | ||
* - If the user has been revoked scopes, it will clear the cached access token and renew one with shrunk scopes. | ||
* | ||
* Note: This hook should only be used once in the ConsoleContent component. | ||
*/ | ||
const useTenantScopeListener = () => { | ||
const { currentTenantId } = useContext(TenantsContext); | ||
const { clearAccessToken, clearAllTokens, getOrganizationTokenClaims, signIn } = useLogto(); | ||
const [tokenClaims, setTokenClaims] = useState<string[]>(); | ||
const redirectUri = useRedirectUri(); | ||
const { scopes = [], isLoading } = useCurrentTenantScopes(); | ||
|
||
useEffect(() => { | ||
(async () => { | ||
const organizationId = getTenantOrganizationId(currentTenantId); | ||
const claims = await getOrganizationTokenClaims(organizationId); | ||
setTokenClaims(claims?.scope?.split(' ') ?? []); | ||
})(); | ||
}, [currentTenantId, getOrganizationTokenClaims]); | ||
|
||
useEffect(() => { | ||
if (isLoading || tokenClaims === undefined) { | ||
return; | ||
} | ||
const hasScopesGranted = scopes.some((scope) => !tokenClaims.includes(scope)); | ||
const hasScopesRevoked = tokenClaims.some((claim) => !scopes.includes(claim)); | ||
if (hasScopesGranted) { | ||
(async () => { | ||
// User has been newly granted scopes. Need to re-consent to obtain the additional scopes. | ||
saveRedirect(); | ||
await clearAllTokens(); | ||
void signIn({ | ||
redirectUri: redirectUri.href, | ||
prompt: Prompt.Consent, | ||
}); | ||
})(); | ||
} | ||
if (hasScopesRevoked) { | ||
// User has been revoked scopes. Need to clear the cached access token and it will be renewed | ||
// automatically with shrunk scopes. | ||
void clearAccessToken(); | ||
} | ||
}, [clearAccessToken, clearAllTokens, isLoading, redirectUri.href, scopes, signIn, tokenClaims]); | ||
}; | ||
|
||
export default useTenantScopeListener; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,61 +1,52 @@ | ||
import { useLogto } from '@logto/react'; | ||
import { TenantScope, getTenantOrganizationId } from '@logto/schemas'; | ||
import { useContext, useEffect, useState } from 'react'; | ||
import { TenantScope } from '@logto/schemas'; | ||
import { useContext, useMemo } from 'react'; | ||
import useSWR from 'swr'; | ||
|
||
import { useAuthedCloudApi } from '@/cloud/hooks/use-cloud-api'; | ||
import { TenantsContext } from '@/contexts/TenantsProvider'; | ||
|
||
import { type RequestError } from './use-api'; | ||
import useCurrentUser from './use-current-user'; | ||
|
||
const useCurrentTenantScopes = () => { | ||
const { currentTenantId, isInitComplete } = useContext(TenantsContext); | ||
const { isAuthenticated, getOrganizationTokenClaims } = useLogto(); | ||
|
||
const [scopes, setScopes] = useState<string[]>([]); | ||
const [canInviteMember, setCanInviteMember] = useState(false); | ||
const [canRemoveMember, setCanRemoveMember] = useState(false); | ||
const [canUpdateMemberRole, setCanUpdateMemberRole] = useState(false); | ||
const [canManageTenant, setCanManageTenant] = useState(false); | ||
const cloudApi = useAuthedCloudApi(); | ||
const { user } = useCurrentUser(); | ||
const userId = user?.id ?? ''; | ||
|
||
useEffect(() => { | ||
(async () => { | ||
if (isAuthenticated && isInitComplete) { | ||
const organizationId = getTenantOrganizationId(currentTenantId); | ||
const claims = await getOrganizationTokenClaims(organizationId); | ||
const allScopes = claims?.scope?.split(' ') ?? []; | ||
setScopes(allScopes); | ||
const { | ||
data: scopes, | ||
isLoading, | ||
mutate, | ||
} = useSWR<string[], RequestError>( | ||
userId && isInitComplete && `api/tenants/${currentTenantId}/members/${userId}/scopes`, | ||
async () => { | ||
const scopes = await cloudApi.get('/api/tenants/:tenantId/members/:userId/scopes', { | ||
params: { tenantId: currentTenantId, userId }, | ||
}); | ||
return scopes.map(({ name }) => name); | ||
} | ||
); | ||
|
||
for (const scope of allScopes) { | ||
switch (scope) { | ||
case TenantScope.InviteMember: { | ||
setCanInviteMember(true); | ||
break; | ||
} | ||
case TenantScope.RemoveMember: { | ||
setCanRemoveMember(true); | ||
break; | ||
} | ||
case TenantScope.UpdateMemberRole: { | ||
setCanUpdateMemberRole(true); | ||
break; | ||
} | ||
case TenantScope.ManageTenant: { | ||
setCanManageTenant(true); | ||
break; | ||
} | ||
default: { | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
})(); | ||
}, [currentTenantId, getOrganizationTokenClaims, isAuthenticated, isInitComplete]); | ||
const access = useMemo( | ||
() => ({ | ||
canInviteMember: Boolean(scopes?.includes(TenantScope.InviteMember)), | ||
canRemoveMember: Boolean(scopes?.includes(TenantScope.RemoveMember)), | ||
canUpdateMemberRole: Boolean(scopes?.includes(TenantScope.UpdateMemberRole)), | ||
canManageTenant: Boolean(scopes?.includes(TenantScope.ManageTenant)), | ||
}), | ||
[scopes] | ||
); | ||
|
||
return { | ||
canInviteMember, | ||
canRemoveMember, | ||
canUpdateMemberRole, | ||
canManageTenant, | ||
scopes, | ||
}; | ||
return useMemo( | ||
() => ({ | ||
isLoading, | ||
scopes, | ||
access, | ||
mutate, | ||
}), | ||
[isLoading, scopes, access, mutate] | ||
); | ||
}; | ||
|
||
export default useCurrentTenantScopes; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.