- {passwordStrengthMessage
+ {(passwordStrengthMessage
? passwordStrengthMessage
- : 'This is the password to your Postgres database, so it must be strong and hard to guess.'}{' '}
+ : 'This is the password to your Postgres database, so it must be strong and hard to guess.') +
+ ' '}
matchFn(error.requestPathname!))
+ SKIP_RETRY_PATHNAME_MATCHERS.some((matchFn) => matchFn(error.requestPathname!)) &&
+ error.code !== 429
) {
return false
}
diff --git a/apps/studio/lib/helpers.ts b/apps/studio/lib/helpers.ts
index d5abad608a4bf..7c4d38c1b0b50 100644
--- a/apps/studio/lib/helpers.ts
+++ b/apps/studio/lib/helpers.ts
@@ -1,4 +1,3 @@
-export { default as passwordStrength } from './password-strength'
export { default as uuidv4 } from './uuid'
import { UIEvent } from 'react'
import type { TablesData } from '../data/tables/tables-query'
diff --git a/apps/studio/lib/password-strength.test.ts b/apps/studio/lib/password-strength.test.ts
index 41f09655ad1c8..9699204014497 100644
--- a/apps/studio/lib/password-strength.test.ts
+++ b/apps/studio/lib/password-strength.test.ts
@@ -1,27 +1,10 @@
import { describe, it, expect, beforeEach, vi } from 'vitest'
-import passwordStrength from './password-strength'
-import { toast } from 'sonner'
-
-// Hoist the post_ mock so it's available before the module is loaded
-const postMock = vi.hoisted(() => vi.fn())
-
-vi.mock('data/fetchers', () => ({
- post: postMock,
-}))
-
-vi.mock('sonner', () => ({
- toast: { error: vi.fn() },
-}))
+import { passwordStrength } from './password-strength'
describe('passwordStrength', () => {
- beforeEach(() => {
- vi.clearAllMocks()
- })
-
it('returns empty values for message, warning and strength for empty input', async () => {
const result = await passwordStrength('')
expect(result).toEqual({ message: '', warning: '', strength: 0 })
- expect(postMock).not.toHaveBeenCalled()
})
it('returns max length message, warning, and strength 0 for password longer than 99 characters', async () => {
@@ -30,51 +13,21 @@ describe('passwordStrength', () => {
expect(result.message).toMatch(/maximum length/i)
expect(result.warning).toMatch(/less than 100 characters/i)
expect(result.strength).toBe(0)
- expect(postMock).not.toHaveBeenCalled()
})
it('returns strong score, suggestion, and empty warning for strong password', async () => {
- postMock.mockResolvedValue({
- data: {
- result: {
- score: 4,
- feedback: { suggestions: ['Successfully updated database password'] },
- },
- },
- error: null,
- })
- const result = await passwordStrength('StrongPassword123!')
+ const result = await passwordStrength('ActuallyAStrongPassword123!')
expect(result.message).toMatch(/strong/i)
- expect(result.message).toContain('Successfully updated database password')
+ expect(result.message).toContain('This password is strong')
expect(result.warning).toBe('')
expect(result.strength).toBe(4)
})
it('returns weak score, suggestion, and warning for weak password', async () => {
- postMock.mockResolvedValue({
- data: {
- result: {
- score: 2,
- feedback: {
- suggestions: ['Try a longer password'],
- warning: 'Too short',
- },
- },
- },
- error: null,
- })
const result = await passwordStrength('weak')
expect(result.message).toMatch(/not secure/i)
- expect(result.message).toContain('Try a longer password')
- expect(result.warning).toMatch(/too short/i)
+ expect(result.message).toContain('This password is not secure enough')
expect(result.warning).toMatch(/you need a stronger password/i)
- expect(result.strength).toBe(2)
- })
-
- it('returns empty values and shows toast error on server error', async () => {
- postMock.mockResolvedValue({ data: null, error: { message: 'Server error' } })
- const result = await passwordStrength('any')
- expect(result).toEqual({ message: '', warning: '', strength: 0 })
- expect(toast.error).toHaveBeenCalledTimes(1)
+ expect(result.strength).toBe(1)
})
})
diff --git a/apps/studio/lib/password-strength.ts b/apps/studio/lib/password-strength.ts
index cf813807e547c..5dd1502e5ce5a 100644
--- a/apps/studio/lib/password-strength.ts
+++ b/apps/studio/lib/password-strength.ts
@@ -1,9 +1,9 @@
-import { post as post_ } from 'data/fetchers'
import { DEFAULT_MINIMUM_PASSWORD_STRENGTH, PASSWORD_STRENGTH } from 'lib/constants'
-import { toast } from 'sonner'
-import { ResponseError } from 'types'
-export default async function passwordStrength(value: string) {
+export async function passwordStrength(value: string) {
+ // [Alaister]: Lazy load zxcvbn to avoid bundling it with the main app (it's pretty chunky)
+ const zxcvbn = await import('zxcvbn').then((module) => module.default)
+
let message: string = ''
let warning: string = ''
let strength: number = 0
@@ -13,29 +13,20 @@ export default async function passwordStrength(value: string) {
message = `${PASSWORD_STRENGTH[0]} Maximum length of password exceeded`
warning = `Password should be less than 100 characters`
} else {
- const { data, error } = await post_('/platform/profile/password-check', {
- body: { password: value },
- })
- if (!error) {
- const { result } = data
- const resultScore = result?.score ?? 0
+ const result = zxcvbn(value)
+ const resultScore = result?.score ?? 0
- const score = (PASSWORD_STRENGTH as any)[resultScore]
- const suggestions = result.feedback?.suggestions
- ? result.feedback.suggestions.join(' ')
- : ''
+ const score = (PASSWORD_STRENGTH as any)[resultScore]
+ const suggestions = result.feedback?.suggestions?.join(' ') ?? ''
- message = `${score} ${suggestions}`
- strength = resultScore
+ message = `${score} ${suggestions}`
+ strength = resultScore
- // warning message for anything below 4 strength :string
- if (resultScore < DEFAULT_MINIMUM_PASSWORD_STRENGTH) {
- warning = `${
- result?.feedback?.warning ? result?.feedback?.warning + '.' : ''
- } You need a stronger password.`
- }
- } else {
- toast.error(`Failed to check password strength: ${(error as ResponseError).message}`)
+ // warning message for anything below 4 strength :string
+ if (resultScore < DEFAULT_MINIMUM_PASSWORD_STRENGTH) {
+ warning = `${
+ result?.feedback?.warning ? result?.feedback?.warning + '.' : ''
+ } You need a stronger password.`
}
}
}
diff --git a/apps/studio/pages/api/platform/profile/password-check.ts b/apps/studio/pages/api/platform/profile/password-check.ts
deleted file mode 100644
index 46c2e932f4984..0000000000000
--- a/apps/studio/pages/api/platform/profile/password-check.ts
+++ /dev/null
@@ -1,23 +0,0 @@
-import { NextApiRequest, NextApiResponse } from 'next'
-import zxcvbn from 'zxcvbn'
-import apiWrapper from 'lib/api/apiWrapper'
-
-export default (req: NextApiRequest, res: NextApiResponse) => apiWrapper(req, res, handler)
-
-async function handler(req: NextApiRequest, res: NextApiResponse) {
- const { method } = req
-
- switch (method) {
- case 'POST':
- return handlePost(req, res)
- default:
- res.setHeader('Allow', ['POST'])
- res.status(405).json({ data: null, error: { message: `Method ${method} Not Allowed` } })
- }
-}
-
-const handlePost = async (req: NextApiRequest, res: NextApiResponse) => {
- const { body } = req
- const result = zxcvbn(body.password)
- return res.status(200).json({ result })
-}
diff --git a/apps/studio/pages/integrations/vercel/[slug]/deploy-button/new-project.tsx b/apps/studio/pages/integrations/vercel/[slug]/deploy-button/new-project.tsx
index 52e49b7aa04e8..d96812b48ada5 100644
--- a/apps/studio/pages/integrations/vercel/[slug]/deploy-button/new-project.tsx
+++ b/apps/studio/pages/integrations/vercel/[slug]/deploy-button/new-project.tsx
@@ -1,6 +1,5 @@
import { useParams } from 'common'
-import { debounce } from 'lodash'
-import { ChangeEvent, useRef, useState } from 'react'
+import { ChangeEvent, useState } from 'react'
import { toast } from 'sonner'
import { Alert, Button, Checkbox, Input, Listbox } from 'ui'
@@ -18,7 +17,7 @@ import { useProjectCreateMutation } from 'data/projects/project-create-mutation'
import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
import { BASE_PATH, PROVIDERS } from 'lib/constants'
import { getInitialMigrationSQLFromGitHubRepo } from 'lib/integration-utils'
-import passwordStrength from 'lib/password-strength'
+import { passwordStrength } from 'lib/password-strength'
import { generateStrongPassword } from 'lib/project'
import { AWS_REGIONS } from 'shared-data'
import { useIntegrationInstallationSnapshot } from 'state/integration-installation'
@@ -63,9 +62,11 @@ const CreateProject = () => {
const snapshot = useIntegrationInstallationSnapshot()
- const delayedCheckPasswordStrength = useRef(
- debounce((value: string) => checkPasswordStrength(value), 300)
- ).current
+ async function checkPasswordStrength(value: string) {
+ const { message, strength } = await passwordStrength(value)
+ setPasswordStrengthScore(strength)
+ setPasswordStrengthMessage(message)
+ }
const { slug, next, currentProjectId: foreignProjectId, externalId } = useParams()
@@ -105,19 +106,13 @@ const CreateProject = () => {
if (value == '') {
setPasswordStrengthScore(-1)
setPasswordStrengthMessage('')
- } else delayedCheckPasswordStrength(value)
- }
-
- async function checkPasswordStrength(value: string) {
- const { message, strength } = await passwordStrength(value)
- setPasswordStrengthScore(strength)
- setPasswordStrengthMessage(message)
+ } else checkPasswordStrength(value)
}
function generatePassword() {
const password = generateStrongPassword()
setDbPass(password)
- delayedCheckPasswordStrength(password)
+ checkPasswordStrength(password)
}
const [newProjectRef, setNewProjectRef] = useState