diff --git a/src/app/AvatarMenu.tsx b/src/app/AvatarMenu.tsx
index d3ee22c0..80391209 100644
--- a/src/app/AvatarMenu.tsx
+++ b/src/app/AvatarMenu.tsx
@@ -5,7 +5,7 @@ import { Button, Header, Menu, MenuItem, MenuSection, MenuTrigger, Popover } fro
import AvatarIcon from '@/app/components/AvatarIcon'
import MenuLinkItem from '@/app/components/MenuLinkItem'
-import authClient from '@/lib/client/auth'
+import authClient, { githubSignin } from '@/lib/client/auth'
import { useThrowToBoundary } from '@/lib/client/util'
import { useConfigCtx } from '@/lib/contexts'
import { setIsAdmin } from '@/lib/server/actions'
@@ -62,15 +62,7 @@ export default function AvatarMenu() {
>
)
} else if (!session.isPending && cfg.hasGithubAuth) {
- return (
-
- )
+ return
} else {
return <>>
}
diff --git a/src/app/[userName]/unauthorized.tsx b/src/app/[userName]/unauthorized.tsx
new file mode 100644
index 00000000..d181597f
--- /dev/null
+++ b/src/app/[userName]/unauthorized.tsx
@@ -0,0 +1,22 @@
+'use client'
+
+import { githubSignin } from '@/lib/client/auth'
+import { useThrowToBoundary } from '@/lib/client/util'
+import { useConfigCtx } from '@/lib/contexts'
+
+export default function UnauthorizedProjects() {
+ const cfg = useConfigCtx()
+ const { throwToBoundary } = useThrowToBoundary()
+
+ return (
+
+
Sign In Required
+
You must be signed in to view profiles or projects.
+ {cfg.hasGithubAuth && (
+
+ )}
+
+ )
+}
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
new file mode 100644
index 00000000..2f8d0148
--- /dev/null
+++ b/src/app/not-found.tsx
@@ -0,0 +1,26 @@
+import Link from 'next/link'
+
+import { getUserSession } from '@/lib/server/auth'
+
+export default async function NotFound() {
+ const session = await getUserSession()
+
+ return (
+
+
404 Not Found
+
Page Not Found.
+
+ {session && (
+ <>
+ Return to your profile.
+ >
+ )}
+ {!session && (
+ <>
+ Return to the home page.
+ >
+ )}
+
+
+ )
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index 223da15f..7fb60e45 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -4,7 +4,7 @@ import { type Route } from 'next'
import Link from 'next/link'
import { useSearchParams } from 'next/navigation'
-import authClient from '@/lib/client/auth'
+import authClient, { githubSignin } from '@/lib/client/auth'
import { useServerAction, useThrowToBoundary } from '@/lib/client/util'
import { type Config, useConfigCtx } from '@/lib/contexts'
import { loginDevUser } from '@/lib/server/actions'
@@ -34,7 +34,7 @@ export default function Root() {
const session = authClient.useSession()
const error = useSearchParams().get('error')
const { throwToBoundary } = useThrowToBoundary()
- const [_, devLoginAction] = useServerAction(loginDevUser, () => session.refetch())
+ const [_, devLoginAction] = useServerAction(loginDevUser, () => window.location.reload())
return (
<>
@@ -56,9 +56,7 @@ export default function Root() {
className='login-link'
disabled={!cfg.hasGithubAuth}
title={!cfg.hasGithubAuth ? 'Ask your administrator to set up GitHub authentication.' : undefined}
- onClick={() => {
- authClient.signIn.social({ provider: 'github' }).catch(throwToBoundary)
- }}
+ onClick={() => githubSignin().catch(throwToBoundary)}
>
GitHub
diff --git a/src/lib/client/auth.ts b/src/lib/client/auth.ts
index 01b773f5..8b096b13 100644
--- a/src/lib/client/auth.ts
+++ b/src/lib/client/auth.ts
@@ -3,9 +3,18 @@ import { createAuthClient } from 'better-auth/react'
import type { AuthInstance } from '@/lib/server/auth'
-export default createAuthClient({
+const authClient = createAuthClient({
// connection errors always throw, this makes non-ok status throw as well so they can be handled similarly
fetchOptions: { throw: true },
// https://better-auth.com/docs/concepts/typescript#inferring-additional-fields-on-client
plugins: [inferAdditionalFields()],
})
+
+export function githubSignin() {
+ return authClient.signIn.social({
+ provider: 'github',
+ callbackURL: window.location.pathname,
+ })
+}
+
+export default authClient
diff --git a/src/lib/server/auth.ts b/src/lib/server/auth.ts
index f3dfa398..9f8cd8d3 100644
--- a/src/lib/server/auth.ts
+++ b/src/lib/server/auth.ts
@@ -212,15 +212,22 @@ export async function getAuth(): Promise {
return g.__auth!
}
-/** Require an authenticated user. Throws `unauthorized()` if not logged in. */
-export async function requireAuth(): Promise {
+/** Check for an authenticated user. Returns `null` if not logged in. */
+export async function getUserSession(): Promise {
await io()
const auth = await getAuth()
const session = await auth.api.getSession({ headers: await headers() })
- if (!session) unauthorized()
+ if (!session) return null
return session
}
+/** Require an authenticated user. Throws `unauthorized()` if not logged in. */
+export async function requireAuth(): Promise {
+ const auth = await getUserSession()
+ if (!auth) unauthorized()
+ return auth
+}
+
/** Require an admin user. Throws `unauthorized()` if not logged in, `forbidden()` if not admin. */
export async function requireAdmin() {
const session = await requireAuth()
diff --git a/src/proxy.ts b/src/proxy.ts
index 24655d23..1761e34f 100644
--- a/src/proxy.ts
+++ b/src/proxy.ts
@@ -1,8 +1,9 @@
import { type NextRequest, NextResponse } from 'next/server'
+import { getUserSession } from './lib/server/auth'
import { getConfig } from './lib/server/config'
-export function proxy(request: NextRequest) {
+export async function proxy(request: NextRequest) {
const cfg = getConfig()
if (!cfg.isSetupComplete) {
const path = request.nextUrl.pathname
@@ -14,6 +15,15 @@ export function proxy(request: NextRequest) {
url.pathname = '/setup'
return NextResponse.redirect(url)
}
+ if (request.nextUrl.pathname === '/') {
+ const session = await getUserSession()
+ if (session) {
+ const url = request.nextUrl.clone()
+ url.pathname = `/${session.user.name}`
+ url.search = ''
+ return NextResponse.redirect(url)
+ }
+ }
return NextResponse.next()
}