Skip to content

Commit

Permalink
[feat] add permissions support in workspaces (#209)
Browse files Browse the repository at this point in the history
  • Loading branch information
sijav authored May 15, 2024
1 parent 8adb11f commit 2284ed9
Show file tree
Hide file tree
Showing 17 changed files with 187 additions and 56 deletions.
9 changes: 7 additions & 2 deletions src/core/auth/AuthGuard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { getAuthData, setAuthData } from 'src/shared/utils/localstorage'
import { TrackJS } from 'trackjs'
import { UserContext, UserContextRealValues } from './UserContext'
import { getCurrentUserQuery } from './getCurrentUser.query'
import { getPermissions, maxPermissionNumber } from './getPermissions'
import { getWorkspacesQuery } from './getWorkspaces.query'
import { logoutMutation } from './logout.mutation'

Expand All @@ -34,6 +35,9 @@ export function AuthGuard({ children }: PropsWithChildren) {
created_at: new Date().toISOString(),
on_hold_since: null,
trial_end_days: null,
user_has_access: true,
user_permissions: maxPermissionNumber,
permissions: getPermissions(maxPermissionNumber),
}
: undefined,
isAuthenticated,
Expand Down Expand Up @@ -75,8 +79,9 @@ export function AuthGuard({ children }: PropsWithChildren) {
const workspaces = await getWorkspacesQuery(instance ?? axiosWithAuth)
setAuth((prev) => {
const selectedWorkspace =
(prev.selectedWorkspace?.id ? workspaces.find((workspace) => workspace.id === prev.selectedWorkspace?.id) : workspaces[0]) ??
workspaces[0]
(prev.selectedWorkspace?.id
? workspaces.find((workspace) => workspace.id === prev.selectedWorkspace?.id)
: workspaces.find((workspace) => workspace.user_has_access && workspace.permissions.includes('read'))) ?? workspaces[0]
window.setTimeout(() => {
window.location.hash = selectedWorkspace?.id ?? ''
})
Expand Down
7 changes: 5 additions & 2 deletions src/core/auth/UserContext.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { createContext } from 'react'
import { GetCurrentUserResponse, GetWorkspaceResponse, GetWorkspacesResponse } from 'src/shared/types/server'
import { Permissions } from './getPermissions'

export type UserContextWorkspace = GetWorkspaceResponse & { permissions: Permissions[] }

export type UserContextRealValues = {
isAuthenticated: boolean
workspaces: GetWorkspacesResponse | never[]
selectedWorkspace?: GetWorkspaceResponse
workspaces: UserContextWorkspace[] | never[]
selectedWorkspace?: UserContextWorkspace
currentUser?: GetCurrentUserResponse
}

Expand Down
24 changes: 24 additions & 0 deletions src/core/auth/getPermissions.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getPermissions, maxPermissionNumber } from './getPermissions'

describe('getPermissions', () => {
it('should return an empty array for zero', () => {
expect(getPermissions(0)).toEqual([])
})

it('should return correct permissions for a single flag', () => {
expect(getPermissions(1)).toEqual(['create']) // 1 << 0
expect(getPermissions(2)).toEqual(['read']) // 1 << 1
expect(getPermissions(8)).toEqual(['delete']) // 1 << 3
})

it('should return multiple permissions for combined flags', () => {
const combined = 13 // 1 << 0 | 1 << 2 | 1 << 3
expect(getPermissions(combined)).toEqual(['create', 'update', 'delete'])
})

it('should handle all flags combined', () => {
expect(getPermissions(maxPermissionNumber).length).toBe(13)
expect(getPermissions(maxPermissionNumber)).toContain('create')
expect(getPermissions(maxPermissionNumber)).toContain('updateRoles')
})
})
27 changes: 27 additions & 0 deletions src/core/auth/getPermissions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
export const workspacePermissions = {
create: 1 << 0, // Not currently used.
read: 1 << 1, // Required to read any content in the workspace; without this, the workspace is effectively disabled.
update: 1 << 2, // Update general workspace properties; not directly related to deletion or invitations.
delete: 1 << 3, // Not currently used.
inviteTo: 1 << 4, // Invite new members to the workspace.
removeFrom: 1 << 5, // Remove existing members from the workspace.
readSettings: 1 << 6, // Access to view workspace settings; necessary to display settings UI.
updateSettings: 1 << 7, // Modify workspace settings.
updateCloudAccounts: 1 << 8, // Manage cloud account integrations within the workspace.
readBilling: 1 << 9, // View billing and subscription details.
updateBilling: 1 << 10, // Modify billing and payment methods.
readRoles: 1 << 11, // View roles of workspace members.
updateRoles: 1 << 12, // Change roles of workspace members.
} as const

export const maxPermissionNumber = (1 << 13) - 1

export type Permissions = keyof typeof workspacePermissions

export const getPermissions = (value: number) =>
Object.entries(workspacePermissions).reduce(
(prev, [permKey, permValue]) => ((value & permValue) === permValue ? [...prev, permKey] : prev),
[] as Permissions[],
)

export const allPermissions = getPermissions(maxPermissionNumber)
5 changes: 4 additions & 1 deletion src/core/auth/getWorkspaces.query.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { AxiosInstance } from 'axios'
import { endPoints } from 'src/shared/constants'
import { GetWorkspacesResponse } from 'src/shared/types/server'
import { getPermissions } from './getPermissions'

export const getWorkspacesQuery = async (axios: AxiosInstance) => {
return axios.get<GetWorkspacesResponse>(endPoints.workspaces.self).then((res) => res.data)
return axios
.get<GetWorkspacesResponse>(endPoints.workspaces.self)
.then((res) => res.data?.map((workspace) => ({ ...workspace, permissions: getPermissions(workspace.user_permissions) })))
}
4 changes: 3 additions & 1 deletion src/core/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
export { AuthGuard } from './AuthGuard'
export { RequireAuth } from './RequireAuth'
export type { UserContextRealValues, UserContextValue } from './UserContext'
export type { UserContextRealValues, UserContextValue, UserContextWorkspace } from './UserContext'
export { getCurrentUserQuery } from './getCurrentUser.query'
export { allPermissions, getPermissions, maxPermissionNumber, workspacePermissions } from './getPermissions'
export type { Permissions } from './getPermissions'
export { useUserProfile } from './useUserProfile'
38 changes: 21 additions & 17 deletions src/locales/de-DE/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ msgstr "Möchten Sie diese Einladung löschen?"
msgid "Do you want to delete this user?"
msgstr "Möchten Sie diesen Benutzer löschen?"

#: src/pages/auth/login/LoginPage.tsx:227
#: src/pages/auth/login/LoginPage.tsx:230
msgid "Don't have an account? Click here to Sign up."
msgstr "Sie haben noch kein Konto? Klicken Sie hier, um sich anzumelden."

Expand All @@ -713,7 +713,7 @@ msgid "Edit"
msgstr "Edit"

#: src/pages/auth/forgot-password/ForgotPasswordPage.tsx:62
#: src/pages/auth/login/LoginPage.tsx:111
#: src/pages/auth/login/LoginPage.tsx:114
#: src/pages/auth/register/RegisterPage.tsx:88
#: src/pages/panel/user-settings/UserSettingsFormEmail.tsx:49
#: src/pages/panel/user-settings/UserSettingsFormEmail.tsx:69
Expand Down Expand Up @@ -845,7 +845,7 @@ msgstr "Für wachsende Teams, die sicher bleiben möchten, während sie die Infr
msgid "For solo software engineers who want to secure a single cloud account."
msgstr "Für Solo-Softwareentwickler, die sich ein einziges Cloud-Konto sichern möchten."

#: src/pages/auth/login/LoginPage.tsx:232
#: src/pages/auth/login/LoginPage.tsx:235
msgid "Forget your password? Click here to reset your password."
msgstr "Passwort vergessen? Klicken Sie hier, um Ihr Passwort zurückzusetzen."

Expand Down Expand Up @@ -1021,8 +1021,8 @@ msgstr "Letzte Anmeldung"
msgid "Load Balancers with no backends"
msgstr "Load Balancer ohne Backends"

#: src/pages/auth/login/LoginPage.tsx:102
#: src/pages/auth/login/LoginPage.tsx:222
#: src/pages/auth/login/LoginPage.tsx:105
#: src/pages/auth/login/LoginPage.tsx:225
msgid "Log in"
msgstr "Anmeldung"

Expand All @@ -1034,7 +1034,7 @@ msgstr "Melden Sie sich mit {formattedName} an"
msgid "Login into the AWS account you want to secure. Deploy a CloudFormation stack that creates a new IAM role for FIX."
msgstr "Melden Sie sich bei dem AWS-Konto an, das Sie sichern möchten. Stellen Sie einen CloudFormation-Stack bereit, der eine neue IAM-Rolle für FIX erstellt."

#: src/shared/layouts/panel-layout/UserProfileButton.tsx:125
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:164
msgid "Logout"
msgstr "Ausloggen"

Expand Down Expand Up @@ -1238,8 +1238,8 @@ msgstr "Geöffnet um"
msgid "Optional professional services"
msgstr "Optionale professionelle Dienstleistungen"

#: src/pages/auth/login/LoginPage.tsx:160
#: src/pages/auth/login/LoginPage.tsx:237
#: src/pages/auth/login/LoginPage.tsx:163
#: src/pages/auth/login/LoginPage.tsx:240
#: src/pages/auth/register/RegisterPage.tsx:135
#: src/pages/panel/user-settings/UserSettingsTotpDeactivationModal.tsx:87
msgid "Or"
Expand All @@ -1261,7 +1261,7 @@ msgstr "Verwaiste Volumes"
msgid "Other Workspace Settings"
msgstr "Andere Arbeitsbereichseinstellungen"

#: src/pages/auth/login/LoginPage.tsx:143
#: src/pages/auth/login/LoginPage.tsx:146
#: src/pages/panel/user-settings/UserSettingsTotpActivationModal.tsx:204
#: src/pages/panel/user-settings/UserSettingsTotpDeactivationModal.tsx:73
msgid "OTP Code"
Expand All @@ -1272,7 +1272,7 @@ msgstr "OTP-Code"
msgid "Owner"
msgstr "Eigentümer"

#: src/pages/auth/login/LoginPage.tsx:126
#: src/pages/auth/login/LoginPage.tsx:129
#: src/pages/auth/register/RegisterPage.tsx:102
#: src/pages/auth/reset-password/ResetPasswordPage.tsx:96
msgid "Password"
Expand Down Expand Up @@ -1301,7 +1301,7 @@ msgstr "Wählen Sie eine der Empfehlungen rechts aus und verbessern Sie Ihre Sic
msgid "Please add a payment method to switch your workspace's product tier"
msgstr ""

#: src/pages/auth/login/LoginPage.tsx:187
#: src/pages/auth/login/LoginPage.tsx:190
msgid "Please enter your One-Time-Password or one of your Recovery code."
msgstr "Bitte geben Sie Ihr One-Time-Passwort oder einen Ihrer Wiederherstellungscodes ein."

Expand Down Expand Up @@ -1345,7 +1345,7 @@ msgstr "Produktsupport per E-Mail, Live-Chat und Videoanruf"
msgid "Product tier changed to {selectedProductTier}"
msgstr ""

#: src/shared/layouts/panel-layout/UserProfileButton.tsx:53
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:92
msgid "Profile"
msgstr "Profil"

Expand Down Expand Up @@ -1377,7 +1377,7 @@ msgstr "Quittungen"
msgid "Recently added accounts"
msgstr "Kürzlich hinzugefügte Konten"

#: src/pages/auth/login/LoginPage.tsx:169
#: src/pages/auth/login/LoginPage.tsx:172
#: src/pages/panel/user-settings/UserSettingsTotpDeactivationModal.tsx:105
msgid "Recovery Code"
msgstr "Wiederherstellungscode"
Expand Down Expand Up @@ -1721,7 +1721,7 @@ msgid "Upgrade"
msgstr "Aktualisierung"

#: src/pages/panel/user-settings/UserSettingsPage.tsx:16
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:117
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:156
msgid "User Settings"
msgstr "Benutzereinstellungen"

Expand Down Expand Up @@ -1749,7 +1749,7 @@ msgstr "Warnung"
msgid "We appreciate your decision to subscribe to our service through AWS Marketplace."
msgstr "Wir freuen uns über Ihre Entscheidung, unseren Service über AWS Marketplace zu abonnieren."

#: src/pages/auth/login/LoginPage.tsx:194
#: src/pages/auth/login/LoginPage.tsx:197
msgid "We have sent an email with a confirmation link to your email address. Please follow the link to activate your account."
msgstr "Wir haben eine E-Mail mit einem Bestätigungslink an Ihre E-Mail-Adresse gesendet. Bitte folgen Sie dem Link, um Ihr Konto zu aktivieren."

Expand Down Expand Up @@ -1827,11 +1827,15 @@ msgstr "Sie können TOTP über den Wiederherstellungscode deaktivieren"
msgid "You don't have access to this workspace"
msgstr ""

#: src/pages/auth/login/LoginPage.tsx:207
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:146
msgid "You don't have the permission to view this workspace, contact the workspace owner for more information."
msgstr ""

#: src/pages/auth/login/LoginPage.tsx:210
msgid "You have successfully reset your password."
msgstr "Sie haben Ihr Passwort erfolgreich zurückgesetzt."

#: src/pages/auth/login/LoginPage.tsx:200
#: src/pages/auth/login/LoginPage.tsx:203
msgid "You have successfully verified your account."
msgstr "Sie haben Ihr Konto erfolgreich verifiziert."

Expand Down
38 changes: 21 additions & 17 deletions src/locales/en-US/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -692,7 +692,7 @@ msgstr "Do you want to delete this invitation?"
msgid "Do you want to delete this user?"
msgstr "Do you want to delete this user?"

#: src/pages/auth/login/LoginPage.tsx:227
#: src/pages/auth/login/LoginPage.tsx:230
msgid "Don't have an account? Click here to Sign up."
msgstr "Don't have an account? Click here to Sign up."

Expand All @@ -713,7 +713,7 @@ msgid "Edit"
msgstr "Edit"

#: src/pages/auth/forgot-password/ForgotPasswordPage.tsx:62
#: src/pages/auth/login/LoginPage.tsx:111
#: src/pages/auth/login/LoginPage.tsx:114
#: src/pages/auth/register/RegisterPage.tsx:88
#: src/pages/panel/user-settings/UserSettingsFormEmail.tsx:49
#: src/pages/panel/user-settings/UserSettingsFormEmail.tsx:69
Expand Down Expand Up @@ -845,7 +845,7 @@ msgstr "For growing teams looking to stay secure as they build out infrastructur
msgid "For solo software engineers who want to secure a single cloud account."
msgstr "For solo software engineers who want to secure a single cloud account."

#: src/pages/auth/login/LoginPage.tsx:232
#: src/pages/auth/login/LoginPage.tsx:235
msgid "Forget your password? Click here to reset your password."
msgstr "Forget your password? Click here to reset your password."

Expand Down Expand Up @@ -1021,8 +1021,8 @@ msgstr "Last login"
msgid "Load Balancers with no backends"
msgstr "Load Balancers with no backends"

#: src/pages/auth/login/LoginPage.tsx:102
#: src/pages/auth/login/LoginPage.tsx:222
#: src/pages/auth/login/LoginPage.tsx:105
#: src/pages/auth/login/LoginPage.tsx:225
msgid "Log in"
msgstr "Log in"

Expand All @@ -1034,7 +1034,7 @@ msgstr "Log in with {formattedName}"
msgid "Login into the AWS account you want to secure. Deploy a CloudFormation stack that creates a new IAM role for FIX."
msgstr "Login into the AWS account you want to secure. Deploy a CloudFormation stack that creates a new IAM role for FIX."

#: src/shared/layouts/panel-layout/UserProfileButton.tsx:125
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:164
msgid "Logout"
msgstr "Logout"

Expand Down Expand Up @@ -1238,8 +1238,8 @@ msgstr "Opened at"
msgid "Optional professional services"
msgstr "Optional professional services"

#: src/pages/auth/login/LoginPage.tsx:160
#: src/pages/auth/login/LoginPage.tsx:237
#: src/pages/auth/login/LoginPage.tsx:163
#: src/pages/auth/login/LoginPage.tsx:240
#: src/pages/auth/register/RegisterPage.tsx:135
#: src/pages/panel/user-settings/UserSettingsTotpDeactivationModal.tsx:87
msgid "Or"
Expand All @@ -1261,7 +1261,7 @@ msgstr "Orphaned Volumes"
msgid "Other Workspace Settings"
msgstr "Other Workspace Settings"

#: src/pages/auth/login/LoginPage.tsx:143
#: src/pages/auth/login/LoginPage.tsx:146
#: src/pages/panel/user-settings/UserSettingsTotpActivationModal.tsx:204
#: src/pages/panel/user-settings/UserSettingsTotpDeactivationModal.tsx:73
msgid "OTP Code"
Expand All @@ -1272,7 +1272,7 @@ msgstr "OTP Code"
msgid "Owner"
msgstr "Owner"

#: src/pages/auth/login/LoginPage.tsx:126
#: src/pages/auth/login/LoginPage.tsx:129
#: src/pages/auth/register/RegisterPage.tsx:102
#: src/pages/auth/reset-password/ResetPasswordPage.tsx:96
msgid "Password"
Expand Down Expand Up @@ -1301,7 +1301,7 @@ msgstr "Pick one of the recommendations to the right and improve your security"
msgid "Please add a payment method to switch your workspace's product tier"
msgstr "Please add a payment method to switch your workspace's product tier"

#: src/pages/auth/login/LoginPage.tsx:187
#: src/pages/auth/login/LoginPage.tsx:190
msgid "Please enter your One-Time-Password or one of your Recovery code."
msgstr "Please enter your One-Time-Password or one of your Recovery code."

Expand Down Expand Up @@ -1345,7 +1345,7 @@ msgstr "Product support via email, live chat, and video call"
msgid "Product tier changed to {selectedProductTier}"
msgstr "Product tier changed to {selectedProductTier}"

#: src/shared/layouts/panel-layout/UserProfileButton.tsx:53
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:92
msgid "Profile"
msgstr "Profile"

Expand Down Expand Up @@ -1377,7 +1377,7 @@ msgstr "Receipts"
msgid "Recently added accounts"
msgstr "Recently added accounts"

#: src/pages/auth/login/LoginPage.tsx:169
#: src/pages/auth/login/LoginPage.tsx:172
#: src/pages/panel/user-settings/UserSettingsTotpDeactivationModal.tsx:105
msgid "Recovery Code"
msgstr "Recovery Code"
Expand Down Expand Up @@ -1721,7 +1721,7 @@ msgid "Upgrade"
msgstr "Upgrade"

#: src/pages/panel/user-settings/UserSettingsPage.tsx:16
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:117
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:156
msgid "User Settings"
msgstr "User Settings"

Expand Down Expand Up @@ -1749,7 +1749,7 @@ msgstr "Warning"
msgid "We appreciate your decision to subscribe to our service through AWS Marketplace."
msgstr "We appreciate your decision to subscribe to our service through AWS Marketplace."

#: src/pages/auth/login/LoginPage.tsx:194
#: src/pages/auth/login/LoginPage.tsx:197
msgid "We have sent an email with a confirmation link to your email address. Please follow the link to activate your account."
msgstr "We have sent an email with a confirmation link to your email address. Please follow the link to activate your account."

Expand Down Expand Up @@ -1827,11 +1827,15 @@ msgstr "You can deactivate TOTP via recovery code"
msgid "You don't have access to this workspace"
msgstr "You don't have access to this workspace"

#: src/pages/auth/login/LoginPage.tsx:207
#: src/shared/layouts/panel-layout/UserProfileButton.tsx:146
msgid "You don't have the permission to view this workspace, contact the workspace owner for more information."
msgstr "You don't have the permission to view this workspace, contact the workspace owner for more information."

#: src/pages/auth/login/LoginPage.tsx:210
msgid "You have successfully reset your password."
msgstr "You have successfully reset your password."

#: src/pages/auth/login/LoginPage.tsx:200
#: src/pages/auth/login/LoginPage.tsx:203
msgid "You have successfully verified your account."
msgstr "You have successfully verified your account."

Expand Down
Loading

0 comments on commit 2284ed9

Please sign in to comment.