ResetDbPassword.tsx194 lines · main
1import { PermissionAction } from '@supabase/shared-types/out/constants'
2import { useParams } from 'common'
3import { useEffect, useState } from 'react'
4import { toast } from 'sonner'
5import { Button, Card, CardContent, Modal } from 'ui'
6import { Input } from 'ui-patterns/DataInputs/Input'
7import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
8import {
9 PageSection,
10 PageSectionContent,
11 PageSectionDescription,
12 PageSectionMeta,
13 PageSectionSummary,
14 PageSectionTitle,
15} from 'ui-patterns/PageSection'
16
17import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
18import { PasswordStrengthBar } from '@/components/ui/PasswordStrengthBar'
19import { useDatabasePasswordResetMutation } from '@/data/database/database-password-reset-mutation'
20import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
21import { useIsProjectActive, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
22import { DEFAULT_MINIMUM_PASSWORD_STRENGTH } from '@/lib/constants'
23import { passwordStrength, PasswordStrengthScore } from '@/lib/password-strength'
24import { generateStrongPassword } from '@/lib/project'
25
26const ResetDbPassword = ({ disabled = false }) => {
27 const { ref } = useParams()
28 const isProjectActive = useIsProjectActive()
29 const { data: project } = useSelectedProjectQuery()
30
31 const { can: canResetDbPassword } = useAsyncCheckPermissions(
32 PermissionAction.UPDATE,
33 'projects',
34 {
35 resource: {
36 project_id: project?.id,
37 },
38 }
39 )
40
41 const [showResetDbPass, setShowResetDbPass] = useState<boolean>(false)
42
43 const [password, setPassword] = useState<string>('')
44 const [passwordStrengthMessage, setPasswordStrengthMessage] = useState<string>('')
45 const [passwordStrengthWarning, setPasswordStrengthWarning] = useState<string>('')
46 const [passwordStrengthScore, setPasswordStrengthScore] = useState(0)
47
48 const { mutate: resetDatabasePassword, isPending: isUpdatingPassword } =
49 useDatabasePasswordResetMutation({
50 onSuccess: async () => {
51 toast.success('Successfully updated database password')
52 setShowResetDbPass(false)
53 },
54 })
55
56 useEffect(() => {
57 if (showResetDbPass) {
58 setPassword('')
59 setPasswordStrengthMessage('')
60 setPasswordStrengthWarning('')
61 setPasswordStrengthScore(0)
62 }
63 }, [showResetDbPass])
64
65 async function checkPasswordStrength(value: any) {
66 const { message, warning, strength } = await passwordStrength(value)
67 setPasswordStrengthScore(strength)
68 setPasswordStrengthWarning(warning)
69 setPasswordStrengthMessage(message)
70 }
71
72 const onDbPassChange = (e: any) => {
73 const value = e.target.value
74 setPassword(value)
75 if (value == '') {
76 setPasswordStrengthScore(-1)
77 setPasswordStrengthMessage('')
78 } else checkPasswordStrength(value)
79 }
80
81 const confirmResetDbPass = async () => {
82 if (!ref) return console.error('Project ref is required')
83
84 if (passwordStrengthScore >= DEFAULT_MINIMUM_PASSWORD_STRENGTH) {
85 resetDatabasePassword({ ref, password })
86 }
87 }
88
89 function generatePassword() {
90 const password = generateStrongPassword()
91 setPassword(password)
92 checkPasswordStrength(password)
93 }
94
95 return (
96 <>
97 <PageSection id="database-password">
98 <PageSectionMeta>
99 <PageSectionSummary>
100 <PageSectionTitle>Database password</PageSectionTitle>
101
102 <PageSectionDescription>Used for direct Postgres connections</PageSectionDescription>
103 </PageSectionSummary>
104 </PageSectionMeta>
105 <PageSectionContent>
106 <Card>
107 <CardContent className="flex flex-row items-center gap-x-2 justify-between">
108 <div className="space-y-0.5">
109 <h3 className="text-sm text-foreground">Reset database password</h3>
110 <p className="text-sm text-foreground-light text-balance">
111 The database password isn’t viewable after creation. Resetting it will break any
112 existing connections.
113 </p>
114 </div>
115
116 <ButtonTooltip
117 type="default"
118 disabled={!canResetDbPassword || !isProjectActive || disabled}
119 onClick={() => setShowResetDbPass(true)}
120 tooltip={{
121 content: {
122 side: 'bottom',
123 text: !canResetDbPassword
124 ? 'You need additional permissions to reset the database password'
125 : !isProjectActive
126 ? 'Unable to reset database password as project is not active'
127 : undefined,
128 },
129 }}
130 >
131 Reset password
132 </ButtonTooltip>
133 </CardContent>
134 </Card>
135 </PageSectionContent>
136 </PageSection>
137 <Modal
138 hideFooter
139 header="Reset database password"
140 confirmText="Reset password"
141 size="medium"
142 visible={showResetDbPass}
143 loading={isUpdatingPassword}
144 onCancel={() => setShowResetDbPass(false)}
145 >
146 <Modal.Content className="w-full space-y-8">
147 <FormItemLayout
148 layout="vertical"
149 isReactForm={false}
150 error={passwordStrengthWarning}
151 description={
152 <PasswordStrengthBar
153 passwordStrengthScore={passwordStrengthScore as PasswordStrengthScore}
154 passwordStrengthMessage={passwordStrengthMessage}
155 password={password}
156 generateStrongPassword={generatePassword}
157 />
158 }
159 >
160 <Input
161 copy={password.length > 0}
162 aria-invalid={!!passwordStrengthWarning}
163 type="password"
164 placeholder="Type in a strong password"
165 value={password}
166 autoComplete="off"
167 onChange={onDbPassChange}
168 />
169 </FormItemLayout>
170 </Modal.Content>
171 <Modal.Separator />
172 <Modal.Content className="flex items-center justify-end space-x-2">
173 <Button
174 type="default"
175 disabled={isUpdatingPassword}
176 onClick={() => setShowResetDbPass(false)}
177 >
178 Cancel
179 </Button>
180 <Button
181 type="primary"
182 loading={isUpdatingPassword}
183 disabled={isUpdatingPassword}
184 onClick={() => confirmResetDbPass()}
185 >
186 Reset password
187 </Button>
188 </Modal.Content>
189 </Modal>
190 </>
191 )
192}
193
194export default ResetDbPassword