ResetDbPassword.tsx 6.5 KB

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