Skip to content

Commit

Permalink
prevent use of common password using the haveibeenpwned password api
Browse files Browse the repository at this point in the history
  • Loading branch information
AdityaKirad committed Mar 6, 2025
1 parent 468c5e5 commit c54b3fd
Show file tree
Hide file tree
Showing 2 changed files with 46 additions and 2 deletions.
22 changes: 22 additions & 0 deletions app/routes/_auth+/onboarding.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import crypto from 'node:crypto'
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { data, redirect, Form, useSearchParams } from 'react-router'
Expand Down Expand Up @@ -72,6 +73,27 @@ export async function action({ request }: Route.ActionArgs) {
})
return
}
const hash = crypto
.createHash('sha1')
.update(data.password, 'utf8')
.digest('hex')
.toUpperCase()
const [prefix, suffix] = [hash.slice(0, 5), hash.slice(5)]
const res = await fetch(
`https://api.pwnedpasswords.com/range/${prefix}`,
)
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)
const text = await res.text()
const matches = text
.split('/\r?\n/')
.filter((line) => line.includes(suffix))
if (matches.length) {
ctx.addIssue({
path: ['password'],
code: 'custom',
message: 'Password is too common',
})
}
}).transform(async (data) => {
if (intent !== null) return { ...data, session: null }

Expand Down
26 changes: 24 additions & 2 deletions app/routes/_auth+/reset-password.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import crypto from 'node:crypto'
import { getFormProps, getInputProps, useForm } from '@conform-to/react'
import { getZodConstraint, parseWithZod } from '@conform-to/zod'
import { type SEOHandle } from '@nasa-gcn/remix-seo'
Expand Down Expand Up @@ -41,8 +42,29 @@ export async function loader({ request }: Route.LoaderArgs) {
export async function action({ request }: Route.ActionArgs) {
const resetPasswordUsername = await requireResetPasswordUsername(request)
const formData = await request.formData()
const submission = parseWithZod(formData, {
schema: ResetPasswordSchema,
const submission = await parseWithZod(formData, {
schema: ResetPasswordSchema.superRefine(async ({ password }, ctx) => {
const hash = crypto
.createHash('sha1')
.update(password, 'utf8')
.digest('hex')
.toUpperCase()
const [prefix, suffix] = [hash.slice(0, 5), hash.slice(5)]
const res = await fetch(`https://api.pwnedpasswords.com/range/${prefix}`)
if (!res.ok) throw new Error(`HTTP error! status: ${res.status}`)
const data = await res.text()
const matches = data
.split('/\r?\n/')
.filter((line) => line.includes(suffix))
if (matches.length) {
ctx.addIssue({
path: ['password'],
code: 'custom',
message: 'Password is too common',
})
}
}),
async: true,
})
if (submission.status !== 'success') {
return data(
Expand Down

0 comments on commit c54b3fd

Please sign in to comment.