Gate the app behind Google sign-in restricted to verified @theodo.com identities, and record the real reporter on filed defects (F9). - nuxt-auth-utils for sealed cookie sessions + the Google OAuth handler, mounted at /auth/google/callback to match the registered redirect URI. - isAllowedGoogleUser re-derives the domain from Google's verified email; the spoofable `hd` claim is deliberately ignored (DESIGN T9). - Default-deny: server middleware 401s unauthenticated /api calls (health and the session endpoint excepted); a global route middleware redirects unauthenticated page navigations to /login. - getReporter() now reads the session email instead of the dev stub. - Env contract moves to nuxt-auth-utils names (NUXT_SESSION_PASSWORD, NUXT_OAUTH_GOOGLE_*); .env.example, compose and README updated. Unit-tested: domain check (incl. hd-spoof + look-alike) and public-path matching. Live OAuth round-trip pending manual verification.
46 lines
1.4 KiB
Vue
46 lines
1.4 KiB
Vue
<script setup lang="ts">
|
|
// Single domain (ADR 0004): report at `/`, view all defects at `/defects`,
|
|
// with a persistent header to move between them.
|
|
const { loggedIn, user, clear } = useUserSession()
|
|
|
|
async function signOut() {
|
|
await clear()
|
|
await navigateTo('/login')
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="min-h-screen">
|
|
<header
|
|
class="navbar bg-base-100 border-base-300 sticky top-0 z-10 border-b px-4 shadow-sm"
|
|
>
|
|
<div class="navbar-start">
|
|
<NuxtLink to="/" class="btn btn-ghost gap-2 text-xl font-semibold">
|
|
<span class="size-2.5 rounded-full bg-[#e11d48]" aria-hidden="true" />
|
|
Project Board Andon
|
|
</NuxtLink>
|
|
</div>
|
|
<nav class="navbar-end">
|
|
<ul class="menu menu-horizontal gap-1 px-0">
|
|
<li>
|
|
<NuxtLink to="/" exact-active-class="menu-active">Report</NuxtLink>
|
|
</li>
|
|
<li>
|
|
<NuxtLink to="/defects" exact-active-class="menu-active">
|
|
View defects
|
|
</NuxtLink>
|
|
</li>
|
|
</ul>
|
|
<div v-if="loggedIn" class="flex items-center gap-3 pl-2">
|
|
<span class="hidden text-sm opacity-70 sm:inline">{{ user?.email }}</span>
|
|
<button type="button" class="btn btn-ghost btn-sm" @click="signOut">
|
|
Sign out
|
|
</button>
|
|
</div>
|
|
</nav>
|
|
</header>
|
|
|
|
<slot />
|
|
</div>
|
|
</template>
|