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
2 changes: 1 addition & 1 deletion apps/blog-next/app/auth-nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { useState } from 'react'
import Link from 'next/link'
import { useAuth } from '@holo-js/adapter-next/client'
import { useAuth } from '@holo-js/auth/next/client'

const linkStyle = {
color: '#cbd5e1',
Expand Down
4 changes: 2 additions & 2 deletions apps/blog-next/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { ReactNode } from 'react'
import type { Metadata } from 'next'
import Link from 'next/link'
import { AuthProvider } from '@holo-js/adapter-next/client'
import { auth } from '@holo-js/adapter-next/server'
import { AuthProvider } from '@holo-js/auth/next/client'
import { auth } from '@holo-js/auth/next/server'

import { AuthNav } from './auth-nav'

Expand Down
3 changes: 2 additions & 1 deletion apps/blog-next/app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import Link from 'next/link'
import { useRouter } from 'next/navigation'
import { useAuth, useForm } from '@holo-js/adapter-next/client'
import { useAuth } from '@holo-js/auth/next/client'
import { useForm } from '@holo-js/adapter-next/client'
import { loginForm } from '@/lib/schemas/auth'

const panelStyle = {
Expand Down
3 changes: 2 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,8 @@ import { useState } from 'react'
import Link from 'next/link'
import { useRouter, useSearchParams } from 'next/navigation'

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

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

Expand Down
18 changes: 12 additions & 6 deletions apps/blog-next/proxy.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
import { guestOnly } from '@holo-js/adapter-next/server'
import { authOnly, guestOnly, protectRoutes } from '@holo-js/auth/next/server'

export const proxy = guestOnly({
routes: ['/login', '/register', '/forgot-password', '/reset-password'],
redirectTo: '/admin',
})
export const proxy = protectRoutes(
guestOnly({
routes: ['/login', '/register', '/forgot-password', '/reset-password'],
redirectTo: '/admin',
}),
authOnly({
routes: ['/admin/*'],
redirectTo: '/login',
}),
)

export const config = {
matcher: ['/login', '/register', '/forgot-password', '/reset-password'],
matcher: ['/login', '/register', '/forgot-password', '/reset-password', '/admin/:path*'],
}
38 changes: 37 additions & 1 deletion apps/blog-next/tests/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,42 @@ async function waitForText(url, predicate, timeoutMs = 30000) {
throw new Error(`Timed out waiting for ${url}${lastError instanceof Error ? `: ${lastError.message}` : ''}`)
}

async function waitForRedirect(url, expectedPath, timeoutMs = 30000) {
const startedAt = Date.now()
let lastError = null

while (Date.now() - startedAt < timeoutMs) {
const remainingMs = timeoutMs - (Date.now() - startedAt)
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), Math.max(100, Math.min(remainingMs, 5000)))
try {
const response = await fetch(url, {
redirect: 'manual',
signal: controller.signal,
})
const location = response.headers.get('location')
if (response.status >= 300 && response.status < 400 && location) {
const locationPath = new URL(location, url).pathname
if (locationPath === expectedPath) {
return
}

lastError = new Error(`Unexpected redirect ${response.status} to ${locationPath}`)
} else {
lastError = new Error(`Unexpected status ${response.status}`)
}
} catch (error) {
lastError = error
} finally {
clearTimeout(timeout)
}

await new Promise(resolve => setTimeout(resolve, 250))
}

throw new Error(`Timed out waiting for ${url} to redirect to ${expectedPath}${lastError instanceof Error ? `: ${lastError.message}` : ''}`)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function pipeOutput(stream, target) {
if (!stream) {
return
Expand Down Expand Up @@ -208,7 +244,7 @@ try {
const initial = await waitForJson(healthUrl, payload => payload.ok === true)
assert.equal(initial.app, 'blog-next')
await waitForText(`http://localhost:${port}/`, payload => payload.includes('Shipping a Real Holo Blog on Next'))
await waitForText(`http://localhost:${port}/admin/posts`, payload => payload.includes('Designing the Example App Roadmap'))
await waitForRedirect(`http://localhost:${port}/admin/posts`, '/login')
await assertExampleAppAuthFlow({
baseUrl: `http://localhost:${port}`,
getOutput: () => capturedOutput,
Expand Down
2 changes: 2 additions & 0 deletions apps/blog-nuxt/app/app.vue
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<script setup lang="ts">
import { useAuth } from '@holo-js/auth/nuxt'

const { authenticated, refreshUser, user } = await useAuth()
const displayName = computed(() => user.value?.name ?? user.value?.email ?? 'Account')

Expand Down
6 changes: 6 additions & 0 deletions apps/blog-nuxt/app/middleware/auth-only.global.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { authOnly } from '@holo-js/auth/nuxt/server'

export default authOnly({
routes: ['/admin/*'],
redirectTo: '/login',
})
2 changes: 1 addition & 1 deletion apps/blog-nuxt/app/middleware/guest-only.global.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { guestOnly } from '@holo-js/adapter-nuxt/server'
import { guestOnly } from '@holo-js/auth/nuxt/server'

export default guestOnly({
routes: ['/login', '/register', '/forgot-password', '/reset-password'],
Expand Down
8 changes: 8 additions & 0 deletions apps/blog-nuxt/app/pages/login.vue
Original file line number Diff line number Diff line change
@@ -1,13 +1,21 @@
<script setup lang="ts">
import { useAuth } from '@holo-js/auth/nuxt'
import { useForm } from '@holo-js/adapter-nuxt/client'
import { loginForm } from '#shared/schemas/auth'

const { refreshUser } = await useAuth()
const form = useForm(loginForm, {
validateOn: 'blur',
initialValues: { email: '', password: '', remember: false },
async submitter({ formData }) {
const submission = await $fetch('/api/login', { method: 'POST', body: formData })
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
try {
await refreshUser()
} catch (error) {
console.warn('Auth refresh failed after login.', error)
}

await navigateTo(submission.data.redirectTo, {
external: true,
})
Expand Down
3 changes: 3 additions & 0 deletions apps/blog-nuxt/app/pages/register.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
<script setup lang="ts">
import { navigateTo } from '#imports'
import { useAuth } from '@holo-js/auth/nuxt'
import { useForm } from '@holo-js/adapter-nuxt/client'
import { registerForm } from '#shared/schemas/auth'

const { refreshUser } = await useAuth()
const form = useForm(registerForm, {
validateOn: 'blur',
initialValues: { name: '', email: '', password: '', passwordConfirmation: '' },
async submitter({ formData }) {
const submission = await $fetch('/api/register', { method: 'POST', body: formData })
if (submission?.ok === true && typeof submission.data?.redirectTo === 'string') {
await refreshUser()
await navigateTo(submission.data.redirectTo, {
redirectCode: 302,
})
Expand Down
1 change: 1 addition & 0 deletions apps/blog-nuxt/app/pages/verify-email.vue
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { navigateTo, useRoute } from '#imports'
import { useAuth } from '@holo-js/auth/nuxt'
import { useForm } from '@holo-js/adapter-nuxt/client'
import { ref } from 'vue'
import { verifyEmailForm } from '#shared/schemas/auth'
Expand Down
30 changes: 29 additions & 1 deletion apps/blog-nuxt/tests/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,34 @@ async function waitForText(url, predicate, timeoutMs = 30000) {
throw new Error(`Timed out waiting for ${url}${lastError instanceof Error ? `: ${lastError.message}` : ''}`)
}

async function waitForRedirect(url, expectedPath, timeoutMs = 30000) {
const startedAt = Date.now()
let lastError = null

while (Date.now() - startedAt < timeoutMs) {
try {
const response = await fetch(url, { redirect: 'manual' })
const location = response.headers.get('location')
if (response.status >= 300 && response.status < 400 && location) {
const locationPath = new URL(location, url).pathname
if (locationPath === expectedPath) {
return
}

lastError = new Error(`Unexpected redirect ${response.status} to ${locationPath}`)
} else {
lastError = new Error(`Unexpected status ${response.status}`)
}
} catch (error) {
lastError = error
}

await new Promise(resolve => setTimeout(resolve, 250))
}

throw new Error(`Timed out waiting for ${url} to redirect to ${expectedPath}${lastError instanceof Error ? `: ${lastError.message}` : ''}`)
}

function pipeOutput(stream, target) {
if (!stream) {
return
Expand Down Expand Up @@ -214,7 +242,7 @@ try {
const initial = await waitForJson(healthUrl, payload => payload.ok === true)
assert.equal(initial.app, 'blog-nuxt')
await waitForText(`http://localhost:${port}/`, payload => payload.includes('Shipping a Real Holo Blog on Nuxt'))
await waitForText(`http://localhost:${port}/admin/posts`, payload => payload.includes('Designing the Example App Roadmap'))
await waitForRedirect(`http://localhost:${port}/admin/posts`, '/login')
await assertExampleAppAuthFlow({
baseUrl: `http://localhost:${port}`,
getOutput: () => capturedOutput,
Expand Down
17 changes: 12 additions & 5 deletions apps/blog-sveltekit/src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { guestOnly } from '@holo-js/adapter-sveltekit/server'
import { sequence } from '@sveltejs/kit/hooks'
import { authOnly, guestOnly } from '@holo-js/auth/sveltekit/server'

export const handle = guestOnly({
routes: ['/login', '/register', '/forgot-password', '/reset-password'],
redirectTo: '/admin',
})
export const handle = sequence(
guestOnly({
routes: ['/login', '/register', '/forgot-password', '/reset-password'],
redirectTo: '/admin',
}),
authOnly({
routes: ['/admin/*'],
redirectTo: '/login',
}),
)
2 changes: 1 addition & 1 deletion apps/blog-sveltekit/src/routes/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { auth } from '@holo-js/adapter-sveltekit/server'
import { auth } from '@holo-js/auth/sveltekit/server'

export async function load() {
const currentAuth = await auth()
Expand Down
2 changes: 1 addition & 1 deletion apps/blog-sveltekit/src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<script lang="ts">
import { invalidateAll } from '$app/navigation'
import { untrack } from 'svelte'
import { useAuth } from '@holo-js/adapter-sveltekit/client'
import { useAuth } from '@holo-js/auth/sveltekit/client'
import type { LayoutProps } from './$types'

let { data, children }: LayoutProps = $props()
Expand Down
3 changes: 2 additions & 1 deletion apps/blog-sveltekit/src/routes/login/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto, invalidateAll } from '$app/navigation'
import { useAuth, useForm } from '@holo-js/adapter-sveltekit/client'
import { useAuth } from '@holo-js/auth/sveltekit/client'
import { useForm } from '@holo-js/adapter-sveltekit/client'
import { loginForm } from '$lib/schemas/auth'

const auth = useAuth()
Expand Down
3 changes: 2 additions & 1 deletion apps/blog-sveltekit/src/routes/register/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto, invalidateAll } from '$app/navigation'
import { useAuth, useForm } from '@holo-js/adapter-sveltekit/client'
import { useAuth } from '@holo-js/auth/sveltekit/client'
import { useForm } from '@holo-js/adapter-sveltekit/client'
import { registerForm } from '$lib/schemas/auth'

const auth = useAuth()
Expand Down
3 changes: 2 additions & 1 deletion apps/blog-sveltekit/src/routes/verify-email/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import { goto, invalidateAll } from '$app/navigation'
import { useAuth, useForm } from '@holo-js/adapter-sveltekit/client'
import { useAuth } from '@holo-js/auth/sveltekit/client'
import { useForm } from '@holo-js/adapter-sveltekit/client'
import { verifyEmailForm } from '$lib/schemas/auth'

export let data: {
Expand Down
38 changes: 37 additions & 1 deletion apps/blog-sveltekit/tests/run.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,42 @@ async function waitForText(url, predicate, timeoutMs = 30000) {
throw new Error(`Timed out waiting for ${url}${lastError instanceof Error ? `: ${lastError.message}` : ''}`)
}

async function waitForRedirect(url, expectedPath, timeoutMs = 30000) {
const startedAt = Date.now()
let lastError = null

while (Date.now() - startedAt < timeoutMs) {
const remainingMs = timeoutMs - (Date.now() - startedAt)
const controller = new AbortController()
const timeout = setTimeout(() => controller.abort(), Math.max(100, Math.min(remainingMs, 5000)))
try {
const response = await fetch(url, {
redirect: 'manual',
signal: controller.signal,
})
const location = response.headers.get('location')
if (response.status >= 300 && response.status < 400 && location) {
const locationPath = new URL(location, url).pathname
if (locationPath === expectedPath) {
return
}

lastError = new Error(`Unexpected redirect ${response.status} to ${locationPath}`)
} else {
lastError = new Error(`Unexpected status ${response.status}`)
}
} catch (error) {
lastError = error
} finally {
clearTimeout(timeout)
}

await new Promise(resolve => setTimeout(resolve, 250))
}

throw new Error(`Timed out waiting for ${url} to redirect to ${expectedPath}${lastError instanceof Error ? `: ${lastError.message}` : ''}`)
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

function pipeOutput(stream, target, onLine) {
if (!stream) {
return
Expand Down Expand Up @@ -263,7 +299,7 @@ try {
const initial = await waitForJson(healthUrl, payload => payload.ok === true)
assert.equal(initial.app, 'blog-sveltekit')
await waitForText(`${devUrl}/`, payload => payload.includes('Shipping a Real Holo Blog on SvelteKit'))
await waitForText(`${devUrl}/admin/posts`, payload => payload.includes('Designing the Example App Roadmap'))
await waitForRedirect(`${devUrl}/admin/posts`, '/login')
await assertExampleAppAuthFlow({
baseUrl: devUrl,
getOutput: () => capturedOutput,
Expand Down
Loading