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
12 changes: 2 additions & 10 deletions src/app/AvatarMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -62,15 +62,7 @@ export default function AvatarMenu() {
</>
)
} else if (!session.isPending && cfg.hasGithubAuth) {
return (
<button
onClick={() => {
authClient.signIn.social({ provider: 'github' }).catch(throwToBoundary)
}}
>
Sign in via GitHub
</button>
)
return <button onClick={() => githubSignin().catch(throwToBoundary)}>Sign in via GitHub</button>
} else {
return <></>
}
Expand Down
22 changes: 22 additions & 0 deletions src/app/[userName]/unauthorized.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h1>Sign In Required</h1>
<p>You must be signed in to view profiles or projects.</p>
{cfg.hasGithubAuth && (
<button style={{ marginTop: 10 }} className='login-link' onClick={() => githubSignin().catch(throwToBoundary)}>
Sign in via GitHub
</button>
)}
</div>
)
}
26 changes: 26 additions & 0 deletions src/app/not-found.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<div>
<h1>404 Not Found</h1>
<p>Page Not Found.</p>
<p>
{session && (
<>
<Link href={`/${session.user.name}`}>Return to your profile</Link>.
</>
)}
{!session && (
<>
<Link href='/'>Return to the home page</Link>.
</>
)}
</p>
</div>
)
}
8 changes: 3 additions & 5 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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 (
<>
Expand All @@ -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
</button>
Expand Down
11 changes: 10 additions & 1 deletion src/lib/client/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<AuthInstance>()],
})

export function githubSignin() {
return authClient.signIn.social({
provider: 'github',
callbackURL: window.location.pathname,
})
}

export default authClient
13 changes: 10 additions & 3 deletions src/lib/server/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,22 @@ export async function getAuth(): Promise<AuthInstance> {
return g.__auth!
}

/** Require an authenticated user. Throws `unauthorized()` if not logged in. */
export async function requireAuth(): Promise<SessionAndUser> {
/** Check for an authenticated user. Returns `null` if not logged in. */
export async function getUserSession(): Promise<null | SessionAndUser> {
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<SessionAndUser> {
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()
Expand Down
12 changes: 11 additions & 1 deletion src/proxy.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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()
}

Expand Down
Loading