Skip to content

Commit 50868e8

Browse files
refactor: keyring ux (#188)
* refactor: sunset local storage for keyrings, update keyring + recovery UX * fix: copy typo * feat: remove ad-hoc sudo passsword dialogs * fix: update sidebar bg * fix: add navbar to workspace selection page * chore: remove unused imports * feat: add utils to store and retrieve sudo password on device * fix: misc fixes to handle route and context changes when switching workspaces * refactor: misc updates to keyring dialog * feat: add util to remove a stored password * fix: add trusted device management in settings * fix: only update route manually if it doesn't match the active organisation * fix: update existing password instead of creating duplicates * feat: add toggle switch component * feat: add shadow to split button meny dropdown * feat: rework 'remember password' style and copy * fix: make trusted device check in settings page more reactive * fix: tweak ui, copy
1 parent 784f09b commit 50868e8

File tree

26 files changed

+707
-634
lines changed

26 files changed

+707
-634
lines changed

frontend/app/[team]/apps/[app]/environments/[environment]/[[...path]]/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,6 @@ export default function Environment({
688688

689689
return (
690690
<div className="max-h-screen overflow-y-auto w-full text-black dark:text-white">
691-
{organisation && <UnlockKeyringDialog organisationId={organisation.id} />}
692691
{keyring !== null && !loading && (
693692
<div className="flex flex-col py-4 gap-4">
694693
<div className="flex items-center gap-8">

frontend/app/[team]/apps/[app]/members/page.tsx

Lines changed: 1 addition & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import { useSession } from 'next-auth/react'
3131
import { Avatar } from '@/components/common/Avatar'
3232
import { KeyringContext } from '@/contexts/keyringContext'
3333
import { unwrapEnvSecretsForUser, wrapEnvSecretsForUser } from '@/utils/environments'
34-
import { OrganisationKeyring, cryptoUtils } from '@/utils/auth'
3534
import { userIsAdmin } from '@/utils/permissions'
3635
import { RoleLabel } from '@/components/users/RoleLabel'
3736
import { Alert } from '@/components/common/Alert'
@@ -40,7 +39,7 @@ import Link from 'next/link'
4039
export default function Members({ params }: { params: { team: string; app: string } }) {
4140
const { data } = useQuery(GetAppMembers, { variables: { appId: params.app } })
4241

43-
const { keyring, setKeyring } = useContext(KeyringContext)
42+
const { keyring } = useContext(KeyringContext)
4443
const { activeOrganisation: organisation } = useContext(organisationContext)
4544

4645
const activeUserIsAdmin = organisation ? userIsAdmin(organisation.role!) : false
@@ -49,21 +48,6 @@ export default function Members({ params }: { params: { team: string; app: strin
4948

5049
const { data: session } = useSession()
5150

52-
const validateKeyring = async (password: string) => {
53-
return new Promise<OrganisationKeyring>(async (resolve) => {
54-
if (keyring) resolve(keyring)
55-
else {
56-
const decryptedKeyring = await cryptoUtils.getKeyring(
57-
session?.user?.email!,
58-
organisation!.id,
59-
password
60-
)
61-
setKeyring(decryptedKeyring)
62-
resolve(decryptedKeyring)
63-
}
64-
})
65-
}
66-
6751
const AddMemberDialog = () => {
6852
const [getMembers, { data: orgMembersData }] = useLazyQuery(GetOrganisationMembers)
6953

@@ -109,8 +93,6 @@ export default function Members({ params }: { params: { team: string; app: strin
10993
const [query, setQuery] = useState('')
11094
const [envScope, setEnvScope] = useState<Array<Record<string, string>>>([])
11195
const [showEnvHint, setShowEnvHint] = useState<boolean>(false)
112-
const [password, setPassword] = useState<string>('')
113-
const [showPw, setShowPw] = useState<boolean>(false)
11496

11597
const filteredPeople =
11698
query === ''
@@ -136,8 +118,6 @@ export default function Members({ params }: { params: { team: string; app: strin
136118
return false
137119
}
138120

139-
const keyring = await validateKeyring(password)
140-
141121
const appEnvironments = appEnvsData.appEnvironments as EnvironmentType[]
142122

143123
const envKeyPromises = appEnvironments
@@ -392,37 +372,6 @@ export default function Members({ params }: { params: { team: string; app: strin
392372
</div>
393373
</Listbox.Options>
394374
</Transition>
395-
396-
{!keyring && (
397-
<div className="space-y-4 w-full">
398-
<label
399-
className="block text-gray-700 text-sm font-bold mb-2"
400-
htmlFor="password"
401-
>
402-
Sudo password
403-
</label>
404-
<div className="flex justify-between w-full bg-zinc-100 dark:bg-zinc-800 ring-1 ring-inset ring-neutral-500/40 focus-within:ring-1 focus-within:ring-inset focus-within:ring-emerald-500 rounded-md p-px">
405-
<input
406-
id="password"
407-
value={password}
408-
onChange={(e) => setPassword(e.target.value)}
409-
type={showPw ? 'text' : 'password'}
410-
minLength={16}
411-
required
412-
autoFocus
413-
className="custom w-full text-zinc-800 font-mono dark:text-white bg-zinc-100 dark:bg-zinc-800 rounded-md ph-no-capture"
414-
/>
415-
<button
416-
className="bg-zinc-100 dark:bg-zinc-800 px-4 text-neutral-500 rounded-md"
417-
type="button"
418-
onClick={() => setShowPw(!showPw)}
419-
tabIndex={-1}
420-
>
421-
{showPw ? <FaEyeSlash /> : <FaEye />}
422-
</button>
423-
</div>
424-
</div>
425-
)}
426375
</>
427376
)}
428377
</Listbox>
@@ -570,8 +519,6 @@ export default function Members({ params }: { params: { team: string; app: strin
570519

571520
const [envScope, setEnvScope] = useState<Array<Record<string, string>>>([])
572521
const [showEnvHint, setShowEnvHint] = useState<boolean>(false)
573-
const [password, setPassword] = useState<string>('')
574-
const [showPw, setShowPw] = useState<boolean>(false)
575522

576523
const memberIsAdmin = userIsAdmin(props.member.role) || false
577524

@@ -618,8 +565,6 @@ export default function Members({ params }: { params: { team: string; app: strin
618565
return false
619566
}
620567

621-
const keyring = await validateKeyring(password)
622-
623568
const appEnvironments = appEnvsData.appEnvironments as EnvironmentType[]
624569

625570
const envKeyPromises = appEnvironments
@@ -803,36 +748,6 @@ export default function Members({ params }: { params: { team: string; app: strin
803748
)}
804749
</Listbox>
805750
</div>
806-
{!keyring && !memberIsAdmin && (
807-
<div className="space-y-2 w-full">
808-
<label
809-
className="block text-gray-700 text-sm font-bold mb-2"
810-
htmlFor="password"
811-
>
812-
Sudo password
813-
</label>
814-
<div className="flex justify-between w-full bg-zinc-100 dark:bg-zinc-800 ring-1 ring-inset ring-neutral-500/40 focus-within:ring-1 focus-within:ring-inset focus-within:ring-emerald-500 rounded-md p-px">
815-
<input
816-
id="password"
817-
value={password}
818-
onChange={(e) => setPassword(e.target.value)}
819-
type={showPw ? 'text' : 'password'}
820-
minLength={16}
821-
required
822-
autoFocus
823-
className="custom w-full text-zinc-800 font-mono dark:text-white bg-zinc-100 dark:bg-zinc-800 rounded-md ph-no-capture"
824-
/>
825-
<button
826-
className="bg-zinc-100 dark:bg-zinc-800 px-4 text-neutral-500 rounded-md"
827-
type="button"
828-
onClick={() => setShowPw(!showPw)}
829-
tabIndex={-1}
830-
>
831-
{showPw ? <FaEyeSlash /> : <FaEye />}
832-
</button>
833-
</div>
834-
</div>
835-
)}
836751

837752
{memberIsAdmin && (
838753
<Alert variant="info" icon={true}>

frontend/app/[team]/apps/[app]/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -578,7 +578,6 @@ export default function Secrets({ params }: { params: { team: string; app: strin
578578

579579
return (
580580
<div className="max-h-screen overflow-y-auto w-full text-black dark:text-white grid gap-16 relative">
581-
{organisation && <UnlockKeyringDialog organisationId={organisation.id} />}
582581
{keyring !== null &&
583582
(setupRequired ? (
584583
<div className="flex flex-col gap-4 w-full items-center p-16">

frontend/app/[team]/apps/[app]/syncing/page.tsx

Lines changed: 1 addition & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ import { userIsAdmin } from '@/utils/permissions'
2727

2828
export default function Syncing({ params }: { params: { team: string; app: string } }) {
2929
const { activeOrganisation: organisation } = useContext(organisationContext)
30-
const { keyring, setKeyring } = useContext(KeyringContext)
30+
const { keyring } = useContext(KeyringContext)
3131

3232
const searchParams = useSearchParams()
3333

@@ -38,25 +38,6 @@ export default function Syncing({ params }: { params: { team: string; app: strin
3838
pollInterval: 10000,
3939
})
4040

41-
const [getCloudflarePages] = useLazyQuery(GetCfPages)
42-
43-
const { data: session } = useSession()
44-
45-
const validateKeyring = async (password: string) => {
46-
return new Promise<OrganisationKeyring>(async (resolve) => {
47-
if (keyring) resolve(keyring)
48-
else {
49-
const decryptedKeyring = await cryptoUtils.getKeyring(
50-
session?.user?.email!,
51-
organisation!.id,
52-
password
53-
)
54-
setKeyring(decryptedKeyring)
55-
resolve(decryptedKeyring)
56-
}
57-
})
58-
}
59-
6041
const activeUserIsAdmin = organisation ? userIsAdmin(organisation.role!) : false
6142

6243
const EnableSyncingDialog = () => {
@@ -71,9 +52,6 @@ export default function Syncing({ params }: { params: { team: string; app: strin
7152

7253
const [isOpen, setIsOpen] = useState<boolean>(false)
7354

74-
const [password, setPassword] = useState<string>('')
75-
const [showPw, setShowPw] = useState<boolean>(false)
76-
7755
const closeModal = () => {
7856
setIsOpen(false)
7957
}
@@ -85,8 +63,6 @@ export default function Syncing({ params }: { params: { team: string; app: strin
8563
const handleEnableSyncing = async (e: { preventDefault: () => void }) => {
8664
e.preventDefault()
8765

88-
const keyring = await validateKeyring(password)
89-
9066
const appEnvironments = appEnvsData.appEnvironments as EnvironmentType[]
9167

9268
const envKeyPromises = appEnvironments.map(async (env: EnvironmentType) => {
@@ -191,37 +167,6 @@ export default function Syncing({ params }: { params: { team: string; app: strin
191167
environments.
192168
</Alert>
193169

194-
{!keyring && (
195-
<div className="space-y-2 w-full">
196-
<label
197-
className="block text-gray-700 text-sm font-bold mb-2"
198-
htmlFor="password"
199-
>
200-
Sudo password
201-
</label>
202-
<div className="flex justify-between w-full bg-zinc-100 dark:bg-zinc-800 ring-1 ring-inset ring-neutral-500/40 focus-within:ring-1 focus-within:ring-inset focus-within:ring-emerald-500 rounded-md p-px">
203-
<input
204-
id="password"
205-
value={password}
206-
onChange={(e) => setPassword(e.target.value)}
207-
type={showPw ? 'text' : 'password'}
208-
minLength={16}
209-
required
210-
autoFocus
211-
className="custom w-full text-zinc-800 font-mono dark:text-white bg-zinc-100 dark:bg-zinc-800 rounded-md ph-no-capture"
212-
/>
213-
<button
214-
className="bg-zinc-100 dark:bg-zinc-800 px-4 text-neutral-500 rounded-md"
215-
type="button"
216-
onClick={() => setShowPw(!showPw)}
217-
tabIndex={-1}
218-
>
219-
{showPw ? <FaEyeSlash /> : <FaEye />}
220-
</button>
221-
</div>
222-
</div>
223-
)}
224-
225170
<div className="flex items-center gap-4">
226171
<Button variant="secondary" type="button" onClick={closeModal}>
227172
Cancel

frontend/app/[team]/apps/[app]/tokens/page.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,6 @@ export default function Tokens({ params }: { params: { team: string; app: string
242242
return (
243243
<div className="w-full overflow-y-auto relative text-black dark:text-white space-y-16">
244244
<section className="max-w-screen-xl">
245-
{organisation && <UnlockKeyringDialog organisationId={organisation.id} />}
246245
{keyring !== null && (
247246
<div className="flex gap-8 mt-6 divide-x divide-neutral-500/20 items-start">
248247
<div className="space-y-4 border-l border-neutral-500/40 h-min">

frontend/app/[team]/layout.tsx

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ import '@/app/globals.css'
44
import { HeroPattern } from '@/components/common/HeroPattern'
55
import { NavBar } from '@/components/layout/Navbar'
66
import Sidebar from '@/components/layout/Sidebar'
7-
import { OrganisationProvider, organisationContext } from '@/contexts/organisationContext'
7+
import { organisationContext } from '@/contexts/organisationContext'
88
import clsx from 'clsx'
99
import { usePathname, useRouter } from 'next/navigation'
1010
import { useContext, useEffect } from 'react'
11-
import { notFound } from 'next/navigation'
11+
12+
import UnlockKeyringDialog from '@/components/auth/UnlockKeyringDialog'
1213

1314
export default function RootLayout({
1415
children,
@@ -29,19 +30,26 @@ export default function RootLayout({
2930
router.push('/signup')
3031
}
3132

32-
// try and get org being access from route params in the list of organisations for this user
33+
// try and get org being accessed from route params in the list of organisations for this user
3334
const org = organisations.find((org) => org.name === params.team)
3435

3536
// update active organisation if it exists
3637
if (org) setActiveOrganisation(org)
37-
// else update the route to the active organisation
38-
else router.push(`/${activeOrganisation!.name}`)
38+
// if there's only one available organisation
39+
else if (organisations.length === 1) setActiveOrganisation(organisations[0])
40+
// else send to home
41+
else router.push(`/`)
3942
}
40-
}, [activeOrganisation, organisations, params.team, router, loading, setActiveOrganisation])
43+
}, [organisations, params.team, router, loading, setActiveOrganisation])
44+
45+
useEffect(() => {
46+
if (activeOrganisation && params.team !== activeOrganisation.name)
47+
router.push(`/${activeOrganisation.name}`)
48+
}, [activeOrganisation, params.team, router])
4149

4250
const path = usePathname()
4351

44-
const showNav = !path?.split('/').includes('newdevice')
52+
const showNav = !path?.split('/').includes('recovery')
4553

4654
return (
4755
<div
@@ -51,6 +59,7 @@ export default function RootLayout({
5159
)}
5260
>
5361
<HeroPattern />
62+
{activeOrganisation && <UnlockKeyringDialog organisation={activeOrganisation} />}
5463
{showNav && <NavBar team={params.team} />}
5564
{showNav && <Sidebar />}
5665
<div className={clsx('min-h-screen overflow-auto', showNav && 'pt-16')}>{children}</div>

frontend/app/[team]/members/page.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -794,9 +794,6 @@ export default function Members({ params }: { params: { team: string } }) {
794794
</table>
795795
</div>
796796
</div>
797-
{activeUserIsAdmin && organisation && (
798-
<UnlockKeyringDialog organisationId={organisation.id} />
799-
)}
800797
</section>
801798
)
802799
}

0 commit comments

Comments
 (0)