Skip to content

Commit a6751ca

Browse files
committed
02/05: drop persona, seed user and verification directly
1 parent dc31e9e commit a6751ca

File tree

5 files changed

+87
-68
lines changed

5 files changed

+87
-68
lines changed

exercises/02.authentication/02.solution.passkeys/tests/db-utils.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,11 @@ export function generateUserInfo() {
3434

3535
export async function createUser() {
3636
const userInfo = generateUserInfo()
37+
const password = 'supersecret'
3738
const user = await prisma.user.create({
3839
data: {
3940
...userInfo,
40-
password: { create: { hash: await getPasswordHash('supersecret') } },
41+
password: { create: { hash: await getPasswordHash(password) } },
4142
},
4243
})
4344

@@ -48,6 +49,7 @@ export async function createUser() {
4849
})
4950
},
5051
...user,
52+
password,
5153
}
5254
}
5355

exercises/02.authentication/05.solution.2fa/tests/db-utils.ts

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1+
import { type generateTOTP } from '@epic-web/totp'
12
import { faker } from '@faker-js/faker'
23
import bcrypt from 'bcryptjs'
34
import { UniqueEnforcer } from 'enforce-unique'
5+
import { twoFAVerifyVerificationType } from '#app/routes/settings+/profile.two-factor.verify.tsx'
6+
import { getPasswordHash } from '#app/utils/auth.server.ts'
7+
import { prisma } from '#app/utils/db.server.ts'
48

59
const uniqueUsernameEnforcer = new UniqueEnforcer()
610

7-
export function createUser() {
11+
export function generateUserInfo() {
812
const firstName = faker.person.firstName()
913
const lastName = faker.person.lastName()
1014

@@ -22,13 +26,57 @@ export function createUser() {
2226
.slice(0, 20)
2327
.toLowerCase()
2428
.replace(/[^a-z0-9_]/g, '_')
29+
2530
return {
2631
username,
2732
name: `${firstName} ${lastName}`,
2833
email: `${username}@example.com`,
2934
}
3035
}
3136

37+
export async function createUser() {
38+
const userInfo = generateUserInfo()
39+
const password = 'supersecret'
40+
const user = await prisma.user.create({
41+
data: {
42+
...userInfo,
43+
password: { create: { hash: await getPasswordHash(password) } },
44+
},
45+
})
46+
47+
return {
48+
async [Symbol.asyncDispose]() {
49+
await prisma.user.deleteMany({
50+
where: { id: user.id },
51+
})
52+
},
53+
...user,
54+
password,
55+
}
56+
}
57+
58+
export async function createVerification(input: {
59+
totp: Awaited<ReturnType<typeof generateTOTP>>
60+
userId: string
61+
}) {
62+
const verification = await prisma.verification.create({
63+
data: {
64+
...input.totp,
65+
type: twoFAVerifyVerificationType,
66+
target: input.userId,
67+
},
68+
})
69+
70+
return {
71+
async [Symbol.asyncDispose]() {
72+
await prisma.verification.deleteMany({
73+
where: { id: verification.id },
74+
})
75+
},
76+
...verification,
77+
}
78+
}
79+
3280
export function createPassword(password: string = faker.internet.password()) {
3381
return {
3482
hash: bcrypt.hashSync(password, 10),
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { generateTOTP } from '@epic-web/totp'
2+
import { createUser, createVerification } from '#tests/db-utils.ts'
3+
import { test, expect } from '#tests/test-extend.ts'
4+
5+
test('authenticates using two-factor authentication (setup)', async ({
6+
navigate,
7+
page,
8+
}) => {
9+
// Create a test user and enable 2FA for them directly in the database.
10+
await using user = await createUser()
11+
const totp = await generateTOTP()
12+
await using _ = await createVerification({
13+
totp,
14+
userId: user.id,
15+
})
16+
17+
// Log in as the created user.
18+
await navigate('/login')
19+
20+
await page.getByLabel('Username').fill(user.username)
21+
await page.getByLabel('Password').fill(user.password)
22+
await page.getByRole('button', { name: 'Log in' }).click()
23+
24+
await page
25+
.getByRole('textbox', { name: /code/i })
26+
.fill((await generateTOTP()).otp)
27+
28+
await page.getByRole('button', { name: 'Submit' }).click()
29+
30+
await expect(page.getByRole('link', { name: user.name! })).toBeVisible()
31+
})

exercises/02.authentication/05.solution.2fa/tests/e2e/authentication-2fa.ts

Lines changed: 0 additions & 22 deletions
This file was deleted.
Lines changed: 4 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,27 @@
1-
import { generateTOTP } from '@epic-web/totp'
21
import { test as testBase, expect } from '@playwright/test'
32
import {
43
definePersona,
54
combinePersonas,
65
type AuthenticateFunction,
76
} from 'playwright-persona'
87
import { href, type Register } from 'react-router'
9-
import { twoFAVerifyVerificationType } from '#app/routes/settings+/profile.two-factor.verify.tsx'
10-
import { getPasswordHash } from '#app/utils/auth.server.ts'
118
import { prisma } from '#app/utils/db.server.ts'
129
import { createUser } from '#tests/db-utils'
1310

1411
interface Fixtures {
1512
navigate: <T extends keyof Register['pages']>(
1613
...args: Parameters<typeof href<T>>
1714
) => Promise<void>
18-
authenticate: AuthenticateFunction<[typeof user, typeof userWith2fa]>
15+
authenticate: AuthenticateFunction<[typeof user]>
1916
}
2017

2118
const user = definePersona('user', {
2219
async createSession({ page }) {
23-
const user = await prisma.user.create({
24-
data: {
25-
...createUser(),
26-
roles: { connect: { name: 'user' } },
27-
password: { create: { hash: await getPasswordHash('supersecret') } },
28-
},
29-
})
20+
const user = await createUser()
3021

3122
await page.goto('/login')
3223
await page.getByLabel('Username').fill(user.username)
33-
await page.getByLabel('Password').fill('supersecret')
24+
await page.getByLabel('Password').fill(user.password)
3425
await page.getByRole('button', { name: 'Log in' }).click()
3526
await page.getByText(user.name!).waitFor({ state: 'visible' })
3627

@@ -47,44 +38,13 @@ const user = definePersona('user', {
4738
},
4839
})
4940

50-
const userWith2fa = definePersona('user-with-2fa', {
51-
async createSession({ page }, testInfo) {
52-
const defaultUserPersona = await user.createSession({ page }, testInfo)
53-
54-
const totp = await generateTOTP()
55-
const verification = await prisma.verification.create({
56-
data: {
57-
...totp,
58-
type: twoFAVerifyVerificationType,
59-
target: defaultUserPersona.user.id,
60-
},
61-
})
62-
63-
return {
64-
user: defaultUserPersona.user,
65-
verification,
66-
}
67-
},
68-
verifySession: user.verifySession,
69-
async destroySession({ session }) {
70-
await Promise.allSettled([
71-
prisma.user.deleteMany({
72-
where: { id: session.user.id },
73-
}),
74-
prisma.verification.deleteMany({
75-
where: { id: session.verification.id },
76-
}),
77-
])
78-
},
79-
})
80-
8141
export const test = testBase.extend<Fixtures>({
8242
async navigate({ page }, use) {
8343
await use(async (...args) => {
8444
await page.goto(href(...args))
8545
})
8646
},
87-
authenticate: combinePersonas(user, userWith2fa),
47+
authenticate: combinePersonas(user),
8848
})
8949

9050
export { expect }

0 commit comments

Comments
 (0)