Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Expand Up @@ -11,7 +11,11 @@ import { toast } from 'react-toastify'
import { PermissionPolicy, isRoleCryptoSafe, userHasGlobalAccess } from '@/utils/access/permissions'
import { RoleLabel } from '@/components/users/RoleLabel'
import { KeyringContext } from '@/contexts/keyringContext'
import { unwrapEnvSecretsForUser, wrapEnvSecretsForAccount } from '@/utils/crypto'
import {
requireEnvironmentKey,
unwrapEnvSecretsForUser,
wrapEnvSecretsForAccount,
} from '@/utils/crypto'
import { userHasPermission } from '@/utils/access/permissions'
import { updateServiceAccountHandlers } from '@/utils/crypto/service-accounts'
import GetOrganisationMembers from '@/graphql/queries/organisation/getOrganisationMembers.gql'
Expand Down Expand Up @@ -101,7 +105,7 @@ export const RoleSelector = (props: {
wrappedSeed: userWrappedSeed,
wrappedSalt: userWrappedSalt,
identityKey,
} = data.environmentKeys[0]
} = requireEnvironmentKey(data.environmentKeys, env.name)

// Unwrap env keys for current logged in user
const { seed, salt } = await unwrapEnvSecretsForUser(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ import { toast } from 'react-toastify'
import { Avatar } from '@/components/common/Avatar'
import { Alert } from '@/components/common/Alert'
import Link from 'next/link'
import { unwrapEnvSecretsForUser, wrapEnvSecretsForAccount } from '@/utils/crypto'
import {
requireEnvironmentKey,
unwrapEnvSecretsForUser,
wrapEnvSecretsForAccount,
} from '@/utils/crypto'
import GenericDialog from '@/components/common/GenericDialog'
import { organisationContext } from '@/contexts/organisationContext'
import { useAppPermissions } from '@/hooks/useAppPermissions'
Expand Down Expand Up @@ -239,7 +243,7 @@ export const AddMemberDialog = ({ appId }: { appId: string }) => {
wrappedSeed: userWrappedSeed,
wrappedSalt: userWrappedSalt,
identityKey,
} = data.environmentKeys[0]
} = requireEnvironmentKey(data.environmentKeys, env.name)

const { seed, salt } = await unwrapEnvSecretsForUser(
userWrappedSeed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ import { userHasGlobalAccess } from '@/utils/access/permissions'
import { useAppPermissions } from '@/hooks/useAppPermissions'
import { Alert } from '@/components/common/Alert'
import Link from 'next/link'
import { arraysEqual, unwrapEnvSecretsForUser, wrapEnvSecretsForAccount } from '@/utils/crypto'
import {
arraysEqual,
requireEnvironmentKey,
unwrapEnvSecretsForUser,
wrapEnvSecretsForAccount,
} from '@/utils/crypto'
import GenericDialog from '@/components/common/GenericDialog'
import { sortEnvs } from '@/utils/secrets'
import { useSearchParams } from 'next/navigation'
Expand Down Expand Up @@ -167,7 +172,7 @@ export const ManageUserAccessDialog = ({
wrappedSeed: userWrappedSeed,
wrappedSalt: userWrappedSalt,
identityKey,
} = data.environmentKeys[0]
} = requireEnvironmentKey(data.environmentKeys, env.name)

const { seed, salt } = await unwrapEnvSecretsForUser(
userWrappedSeed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,11 @@ import { KeyringContext } from '@/contexts/keyringContext'
import { useAppPermissions } from '@/hooks/useAppPermissions'
import { Alert } from '@/components/common/Alert'
import Link from 'next/link'
import { unwrapEnvSecretsForUser, wrapEnvSecretsForAccount } from '@/utils/crypto'
import {
requireEnvironmentKey,
unwrapEnvSecretsForUser,
wrapEnvSecretsForAccount,
} from '@/utils/crypto'
import { useSearchParams, useParams } from 'next/navigation'
import GenericDialog from '@/components/common/GenericDialog'
import { EmptyState } from '@/components/common/EmptyState'
Expand Down Expand Up @@ -242,7 +246,7 @@ export const AddAccountDialog = ({ appId }: { appId: string }) => {
wrappedSeed: userWrappedSeed,
wrappedSalt: userWrappedSalt,
identityKey,
} = data.environmentKeys[0]
} = requireEnvironmentKey(data.environmentKeys, env.name)

const { seed, salt } = await unwrapEnvSecretsForUser(
userWrappedSeed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,12 @@ import { userHasGlobalAccess } from '@/utils/access/permissions'
import { useAppPermissions } from '@/hooks/useAppPermissions'
import { Alert } from '@/components/common/Alert'
import Link from 'next/link'
import { arraysEqual, unwrapEnvSecretsForUser, wrapEnvSecretsForAccount } from '@/utils/crypto'
import {
arraysEqual,
requireEnvironmentKey,
unwrapEnvSecretsForUser,
wrapEnvSecretsForAccount,
} from '@/utils/crypto'
import GenericDialog from '@/components/common/GenericDialog'
import { sortEnvs } from '@/utils/secrets'
import { useSearchParams } from 'next/navigation'
Expand Down Expand Up @@ -171,7 +176,7 @@ export const ManageAccountAccessDialog = ({
wrappedSeed: userWrappedSeed,
wrappedSalt: userWrappedSalt,
identityKey,
} = data.environmentKeys[0]
} = requireEnvironmentKey(data.environmentKeys, env.name)

const { seed, salt } = await unwrapEnvSecretsForUser(
userWrappedSeed,
Expand Down
8 changes: 6 additions & 2 deletions frontend/components/apps/EnableSSEDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,11 @@ import GetAppSyncStatus from '@/graphql/queries/syncing/getAppSyncStatus.gql'
import { GetAppDetail } from '@/graphql/queries/getAppDetail.gql'
import { FaServer } from 'react-icons/fa6'
import { organisationContext } from '@/contexts/organisationContext'
import { unwrapEnvSecretsForUser, wrapEnvSecretsForServer } from '@/utils/crypto'
import {
requireEnvironmentKey,
unwrapEnvSecretsForUser,
wrapEnvSecretsForServer,
} from '@/utils/crypto'
import { useAppPermissions } from '@/hooks/useAppPermissions'
import Link from 'next/link'

Expand Down Expand Up @@ -66,7 +70,7 @@ export const EnableSSEDialog = (props: { appId: string }) => {
wrappedSeed: userWrappedSeed,
wrappedSalt: userWrappedSalt,
identityKey,
} = envKeyData.environmentKeys[0]
} = requireEnvironmentKey(envKeyData.environmentKeys, env.name)

const { seed, salt } = await unwrapEnvSecretsForUser(
userWrappedSeed,
Expand Down
3 changes: 2 additions & 1 deletion frontend/components/apps/tokens/CreateServiceTokenDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
newServiceTokenKeys,
splitSecret,
getWrappedKeyShare,
requireEnvironmentKey,
unwrapEnvSecretsForUser,
wrapEnvSecretsForServiceToken,
} from '@/utils/crypto'
Expand Down Expand Up @@ -123,7 +124,7 @@ export const CreateServiceTokenDialog = (props: { organisationId: string; appId:
wrappedSeed: userWrappedSeed,
wrappedSalt: userWrappedSalt,
identityKey,
} = data.environmentKeys[0]
} = requireEnvironmentKey(data.environmentKeys, env.name)

const { seed, salt } = await unwrapEnvSecretsForUser(
userWrappedSeed,
Expand Down
6 changes: 5 additions & 1 deletion frontend/components/environments/secrets/HistoryDialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
getUserKxPrivateKey,
decryptAsymmetric,
envKeyring,
requireEnvironmentKey,
} from '@/utils/crypto'
import { KeyringContext } from '@/contexts/keyringContext'
import Spinner from '@/components/common/Spinner'
Expand Down Expand Up @@ -77,7 +78,10 @@ export const HistoryDialog = ({
})

if (data && keyring) {
const wrappedSeed = data.environmentKeys[0].wrappedSeed
const wrappedSeed = requireEnvironmentKey(
data.environmentKeys,
secret.environment.name
).wrappedSeed

const userKxKeys = {
publicKey: await getUserKxPublicKey(keyring.publicKey),
Expand Down
32 changes: 32 additions & 0 deletions frontend/utils/crypto/environments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,38 @@ export const wrapEnvSecretsForServiceToken = async (
}
}

/**
* Read the viewer's own environment key out of a `GetEnvironmentKey` result.
*
* `environmentKeys` resolves to a queryset filtered by member, so an empty list
* is a normal outcome rather than an error state: it means the acting member
* holds no key for this environment, e.g. they were given app access without
* that environment, or the environment was created after their keys were
* provisioned. Indexing `[0]` and destructuring it turns that case into
* "Cannot destructure property 'wrappedSeed' of 'undefined'" thrown from inside
* an async callback, which tells the user nothing and, at the call sites that
* do not catch, leaves the dialog looking inert.
*
* @param {EnvironmentKeyType[] | null | undefined} environmentKeys - `data.environmentKeys` from the query.
* @param {string} [envName] - Environment name, used to make the error actionable.
* @returns {EnvironmentKeyType} - The viewer's environment key.
* @throws {Error} - If the viewer holds no key for this environment.
*/
export const requireEnvironmentKey = (
environmentKeys: EnvironmentKeyType[] | null | undefined,
envName?: string
): EnvironmentKeyType => {
const environmentKey = environmentKeys?.[0]

if (!environmentKey) {
throw new Error(
`You don't have a key for the ${envName ?? 'selected'} environment, so it can't be shared. Ask an admin to grant you access to it and try again.`
)
}

return environmentKey
}

/**
* Unwraps environment secrets for a user.
*
Expand Down