Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions apps/blog-next/app/api/verify-email/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { verification } from '@holo-js/auth'
import { check, verification } from '@holo-js/auth'
import { sanitizeFlashedInput, validate } from '@holo-js/forms'

import { verifyEmailForm } from '@/lib/schemas/auth'
Expand All @@ -12,6 +12,7 @@ export async function POST(request: Request) {
})
}

const wasAuthenticated = await check()
const { error } = await verification.consume(submission.data.token)
if (error) {
return Response.json({
Expand All @@ -26,7 +27,9 @@ export async function POST(request: Request) {
}

return Response.json(submission.success({
message: 'Email address verified. You can sign in now.',
redirectTo: '/login',
message: wasAuthenticated
? 'Email address verified.'
: 'Email address verified. You can sign in now.',
redirectTo: wasAuthenticated ? '/admin' : '/login',
}))
}
4 changes: 3 additions & 1 deletion apps/blog-next/app/verify-email/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useState } from 'react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'

import { useForm } from '@holo-js/adapter-next/client'
import { useAuth, useForm } from '@holo-js/adapter-next/client'

import { verifyEmailForm } from '@/lib/schemas/auth'

Expand All @@ -21,6 +21,7 @@ const panelStyle = {

function VerifyEmailPageContent() {
const router = useRouter()
const auth = useAuth()
const searchParams = useSearchParams()
const token = searchParams.get('token') ?? ''
const email = searchParams.get('email') ?? ''
Expand All @@ -34,6 +35,7 @@ function VerifyEmailPageContent() {
const response = await fetch('/api/verify-email', { method: 'POST', body: formData })
const submission = await response.json()
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
await auth.refreshUser()
router.replace(submission.data.redirectTo)
}
return submission
Expand Down
10 changes: 8 additions & 2 deletions apps/blog-nuxt/pages/verify-email.vue
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { useRoute } from '#imports'
import { navigateTo, useRoute } from '#imports'
import { useForm } from '@holo-js/adapter-nuxt/client'
import { ref } from 'vue'
import { verifyEmailForm } from '~/lib/schemas/auth'

const { refreshUser } = await useAuth()
const route = useRoute()
const token = typeof route.query.token === 'string' ? route.query.token : ''
const email = typeof route.query.email === 'string' ? route.query.email : ''
Expand All @@ -14,7 +15,12 @@ const resending = ref(false)
const form = useForm(verifyEmailForm, {
initialValues: { token },
async submitter({ formData }) {
return await $fetch('/api/verify-email', { method: 'POST', body: formData })
const submission = await $fetch('/api/verify-email', { method: 'POST', body: formData })
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
await refreshUser()
await navigateTo(submission.data.redirectTo)
}
return submission
},
})

Expand Down
9 changes: 6 additions & 3 deletions apps/blog-nuxt/server/api/verify-email.post.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { verification } from '@holo-js/auth'
import { check, verification } from '@holo-js/auth'
import { sanitizeFlashedInput, validate } from '@holo-js/forms'

import { verifyEmailForm } from '../../lib/schemas/auth'
Expand All @@ -12,6 +12,7 @@ export default defineEventHandler(async (event) => {
return failure
}

const wasAuthenticated = await check()
const { error } = await verification.consume(submission.data.token)
if (error) {
setResponseStatus(event, error.status)
Expand All @@ -25,7 +26,9 @@ export default defineEventHandler(async (event) => {
}

return submission.success({
message: 'Email address verified. You can sign in now.',
redirectTo: '/login',
message: wasAuthenticated
? 'Email address verified.'
: 'Email address verified. You can sign in now.',
redirectTo: wasAuthenticated ? '/admin' : '/login',
})
})
2 changes: 1 addition & 1 deletion apps/blog-sveltekit/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const appEnv = env('APP_ENV') === 'production'
export default defineAppConfig({
name: env('APP_NAME', 'blog-sveltekit'),
key: env('APP_KEY'),
url: env('APP_URL', 'http://localhost:3000'),
url: env('APP_URL', 'http://localhost:5173'),
env: appEnv,
debug: env('APP_DEBUG', true),
paths: {
Expand Down
2 changes: 1 addition & 1 deletion apps/blog-sveltekit/config/broadcast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export default defineBroadcastConfig({
useTLS: broadcastScheme === 'https',
},
clientOptions: {
authEndpoint: `${env('APP_URL', 'http://localhost:3000')}/broadcasting/auth`,
authEndpoint: `${env('APP_URL', 'http://localhost:5173')}/broadcasting/auth`,
},
},
log: {
Expand Down
12 changes: 8 additions & 4 deletions apps/blog-sveltekit/src/routes/api/verify-email/+server.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { json } from '@sveltejs/kit'
import { verification } from '@holo-js/auth'
import { check, verification } from '@holo-js/auth'
import { sanitizeFlashedInput, validate } from '@holo-js/forms'

import { verifyEmailForm } from '$lib/schemas/auth'
Expand All @@ -13,7 +13,9 @@ export async function POST({ request }: { request: Request }) {
})
}

const { error } = await verification.consume(submission.data.token)
const authenticationCheck = check()
const verificationResult = verification.consume(submission.data.token)
const [wasAuthenticated, { error }] = await Promise.all([authenticationCheck, verificationResult])
if (error) {
return json({
ok: false as const,
Expand All @@ -27,7 +29,9 @@ export async function POST({ request }: { request: Request }) {
}

return json(submission.success({
message: 'Email address verified. You can sign in now.',
redirectTo: '/login',
message: wasAuthenticated
? 'Email address verified.'
: 'Email address verified. You can sign in now.',
redirectTo: wasAuthenticated ? '/admin' : '/login',
}))
}
7 changes: 5 additions & 2 deletions apps/blog-sveltekit/src/routes/login/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { useForm } from '@holo-js/adapter-sveltekit/client'
import { goto, invalidateAll } from '$app/navigation'
import { useAuth, useForm } from '@holo-js/adapter-sveltekit/client'
import { loginForm } from '$lib/schemas/auth'

const auth = useAuth()
const form = useForm(loginForm, {
validateOn: 'blur',
initialValues: { email: '', password: '', remember: false },
async submitter({ formData }) {
const response = await fetch('/api/login', { method: 'POST', body: formData })
const submission = await response.json()
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
await auth.refreshUser()
await invalidateAll()
await goto(submission.data.redirectTo)
}
return submission
Expand Down
7 changes: 5 additions & 2 deletions apps/blog-sveltekit/src/routes/register/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
<script lang="ts">
import { goto } from '$app/navigation'
import { useForm } from '@holo-js/adapter-sveltekit/client'
import { goto, invalidateAll } from '$app/navigation'
import { useAuth, useForm } from '@holo-js/adapter-sveltekit/client'
import { registerForm } from '$lib/schemas/auth'

const auth = useAuth()
const form = useForm(registerForm, {
validateOn: 'blur',
initialValues: { name: '', email: '', password: '', passwordConfirmation: '' },
async submitter({ formData }) {
const response = await fetch('/api/register', { method: 'POST', body: formData })
const submission = await response.json()
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
await auth.refreshUser()
await invalidateAll()
await goto(submission.data.redirectTo)
}
return submission
Expand Down
16 changes: 14 additions & 2 deletions apps/blog-sveltekit/src/routes/verify-email/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script lang="ts">
import { useForm } from '@holo-js/adapter-sveltekit/client'
import { goto, invalidateAll } from '$app/navigation'
import { useAuth, useForm } from '@holo-js/adapter-sveltekit/client'
import { verifyEmailForm } from '$lib/schemas/auth'

export let data: {
Expand All @@ -11,11 +12,22 @@
let resendError = ''
let resending = false

const auth = useAuth()
const form = useForm(verifyEmailForm, {
initialValues: { token: data.token },
async submitter({ formData }) {
const response = await fetch('/api/verify-email', { method: 'POST', body: formData })
return await response.json()
const submission = await response.json()
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
try {
await auth.refreshUser()
} catch (error) {
console.error('Failed to refresh the current user after email verification.', error)
}
await invalidateAll()
await goto(submission.data.redirectTo)
}
return submission
},
})

Expand Down
38 changes: 38 additions & 0 deletions tests/example-app-auth-flow.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,44 @@ export async function assertExampleAppAuthFlow({
assert.equal(initialUser.json.authenticated, false)
assert.equal(initialUser.json.user, null)

const loggedInVerificationEmail = `${appName}-logged-in-verification-${Date.now()}@app.test`
const loggedInVerificationOutputStart = getOutput().length
const loggedInVerificationJar = createCookieJar()
const loggedInVerification = await fetchAuthJson('/api/register', {
fields: {
name: 'Logged In Verification User',
email: loggedInVerificationEmail,
password,
passwordConfirmation: password,
},
headers: {
'x-forwarded-for': '127.0.0.221',
'x-real-ip': '127.0.0.221',
},
jar: loggedInVerificationJar,
})
assert.equal(loggedInVerification.response.status, 201)
assert.equal(loggedInVerification.json.ok, true)

const loggedInVerificationToken = (
await waitForOutputMatch(
getOutput,
authTokenPattern,
loggedInVerificationOutputStart,
)
)[1]
assert.ok(loggedInVerificationToken)

const loggedInVerified = await fetchAuthJson('/api/verify-email', {
method: 'POST',
fields: {
token: loggedInVerificationToken,
},
jar: loggedInVerificationJar,
})
assert.equal(loggedInVerified.json.ok, true)
assert.equal(loggedInVerified.json.data?.redirectTo, '/admin')

const registerOutputStart = getOutput().length
const registeredJar = createCookieJar()
const registered = await fetchAuthJson('/api/register', {
Expand Down