Skip to content

Commit

Permalink
Merge pull request #1 from nilsroehrig/new-auth
Browse files Browse the repository at this point in the history
new-auth
  • Loading branch information
rabend authored Nov 10, 2023
2 parents 8766f14 + f1990cb commit 0dde088
Show file tree
Hide file tree
Showing 10 changed files with 291 additions and 303 deletions.
2 changes: 1 addition & 1 deletion .firebaserc
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"projects": {
"default": "fir-playground-22c29"
"default": "plant-care-assistant-38fd3"
}
}
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ node_modules
vite.config.js.timestamp-*
vite.config.ts.timestamp-*
*.log
.firebase
.firebase
.idea
11 changes: 3 additions & 8 deletions src/hooks.server.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import { redirect } from '@sveltejs/kit';
import { getAuth } from 'firebase-admin/auth';
import functions from 'firebase-functions';
import { adminApp } from './lib/server/firebase';
import { adminApp } from '$lib/server/firebase';

export async function handle({ resolve, event }) {
event.locals.app = adminApp();

if (event.url.pathname.startsWith('/app')) {
// TODO: the client side cookie is seemingly stripped by firebase, so the idToken must be transferred another way (see login/+page.svelte).
// Using a session cookie is recommended by the docs: https://firebase.google.com/docs/auth/admin/manage-cookies?hl=en
const idToken = event.cookies.get('idToken');

functions.logger.info(`ID TOKEN: ${idToken}`);

const idToken = event.cookies.get('__session');
if (!idToken) {
throw redirect(302, '/login');
}

try {
const decodedIdToken = await getAuth(event.locals.app).verifyIdToken(idToken);
const decodedIdToken = await getAuth(event.locals.app).verifySessionCookie(idToken, true);
event.locals.uid = decodedIdToken.uid;
} catch (e) {
functions.logger.error(e);
Expand Down
5 changes: 2 additions & 3 deletions src/routes/+layout.server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { redirect } from '@sveltejs/kit';
import { redirect, type ServerLoadEvent } from '@sveltejs/kit';


export function load({ url }) {
export function load({ url }: ServerLoadEvent) {
if (url.pathname === '/') {
throw redirect(302, '/app');
}
Expand Down
253 changes: 122 additions & 131 deletions src/routes/+layout.svelte
Original file line number Diff line number Diff line change
@@ -1,143 +1,134 @@
<script lang="ts" context="module">
type FirebaseStoreData = {
app: FirebaseApp | null;
auth: Auth | null;
};
type FirebaseStoreData = {
app: FirebaseApp | null;
auth: Auth | null;
};
export type FirebaseStore = Readable<FirebaseStoreData>;
export type FirebaseStore = Readable<FirebaseStoreData>;
</script>

<script lang="ts">
import '$lib/styles/global.css';
import '@picocss/pico';
import { browser, dev } from '$app/environment';
import { config } from '$lib/config/firebaseConfig';
import { initializeApp, type FirebaseApp } from 'firebase/app';
import {
browserSessionPersistence,
getAuth,
onAuthStateChanged,
onIdTokenChanged,
signOut,
type Auth,
type Unsubscribe
} from 'firebase/auth';
import Cookies from 'js-cookie';
import { onDestroy, setContext } from 'svelte';
import { Icon, Plus } from 'svelte-hero-icons';
import { readonly, writable, type Readable, type Writable } from 'svelte/store';
import { goto } from '$app/navigation';
const subscriptions: Unsubscribe[] = [];
const firebaseStore: Writable<FirebaseStoreData> = writable({
app: null,
auth: null
});
if (browser) {
// Initialize Firebase
const app = initializeApp(config);
const auth = getAuth(app);
auth.setPersistence(browserSessionPersistence).catch(console.error);
subscriptions.push(
onAuthStateChanged(auth, () => {
firebaseStore.set({
app,
auth
});
})
);
subscriptions.push(
// TODO: as stated in login/+page.svelte, the cookie is not working for us. Instead, e.g. a session refresh could be triggered, once the idToken changes.
onIdTokenChanged(auth, (user) => {
user
?.getIdToken()
.then((token) => Cookies.set('idToken', token, { expires: 1 / 24, sameSite: 'Strict' }))
.catch(console.error);
})
);
}
setContext('firebase', readonly(firebaseStore));
let isLoggingOut = false;
function logout() {
isLoggingOut = true;
if ($firebaseStore.auth == null) {
return;
}
return signOut($firebaseStore.auth)
.then(() => {
Cookies.remove('idToken');
goto('/');
})
.catch(console.error)
.finally(() => (isLoggingOut = false));
}
onDestroy(() => {
subscriptions.forEach((unsubscribe) => unsubscribe());
subscriptions.length = 0;
});
import '$lib/styles/global.css';
import '@picocss/pico';
import { browser, dev } from '$app/environment';
import { config } from '$lib/config/firebaseConfig';
import { initializeApp, type FirebaseApp } from 'firebase/app';
import {
browserSessionPersistence,
getAuth,
onAuthStateChanged,
onIdTokenChanged,
signOut,
type Auth,
type Unsubscribe
} from 'firebase/auth';
import Cookies from 'js-cookie';
import { onDestroy, setContext } from 'svelte';
import { Icon, Plus } from 'svelte-hero-icons';
import { readonly, writable, type Readable, type Writable } from 'svelte/store';
import { goto } from '$app/navigation';
import { enhance } from '$app/forms';
const subscriptions: Unsubscribe[] = [];
const firebaseStore: Writable<FirebaseStoreData> = writable({
app: null,
auth: null
});
if (browser) {
// Initialize Firebase
const app = initializeApp(config);
const auth = getAuth(app);
auth.setPersistence(browserSessionPersistence).catch(console.error);
subscriptions.push(
onAuthStateChanged(auth, () => {
firebaseStore.set({
app,
auth
});
})
);
subscriptions.push(
onIdTokenChanged(auth, (user) => {
user
?.getIdToken()
.then((token) => Cookies.set('idToken', token, {expires: 1 / 24, sameSite: 'Strict'}))
.catch(console.error);
})
);
}
setContext('firebase', readonly(firebaseStore));
let isLoggingOut = false;
onDestroy(() => {
subscriptions.forEach((unsubscribe) => unsubscribe());
subscriptions.length = 0;
});
</script>

<div class="wrapper">
<header class="container">
<nav>
<ul>
<li><strong>Plant Care Assistant</strong></li>
</ul>
<ul>
{#if $firebaseStore.auth?.currentUser}
<li>
<a href="/app/create-plant" role="button" class="outline"
><Icon src={Plus} size="20" /> Create Plant</a
>
</li>
<li>
<a
href="/logout"
role="button"
aria-busy={isLoggingOut}
class="secondary outline"
on:click|preventDefault={logout}>Logout</a
>
</li>
{:else}
<li><a href="/login" role="button" class="secondary outline">Login</a></li>
{/if}
</ul>
</nav>
</header>

<slot />

<footer class="container">SvelteKit &hearts; Firebase</footer>
<header class="container">
<nav>
<ul>
<li><strong>Plant Care Assistant</strong></li>
</ul>
<ul>
{#if $firebaseStore.auth?.currentUser}
<li>
<a href="/app/create-plant" role="button" class="outline"
>
<Icon src={Plus} size="20"/>
Create Plant</a
>
</li>
<li>
<form method="POST" action="/login?/logout" use:enhance={async () => {
isLoggingOut = true;
try {
await signOut($firebaseStore.auth)
} catch(e) {
console.error(e)
} finally {
isLoggingOut = false;
}
}}>
<button type="submit" class="secondary outline">Logout</button>
</form>
</li>
{:else}
<li><a href="/login" role="button" class="secondary outline">Login</a></li>
{/if}
</ul>
</nav>
</header>

<slot/>

<footer class="container">SvelteKit &hearts; Firebase</footer>
</div>

<style>
.wrapper {
display: grid;
min-height: 100vh;
grid-template-rows: min-content 1fr min-content;
}
li a {
display: flex;
align-items: center;
}
footer {
padding: 1rem 0;
font-size: smaller;
text-align: center;
}
.wrapper {
display: grid;
min-height: 100vh;
grid-template-rows: min-content 1fr min-content;
}
li a {
display: flex;
align-items: center;
}
footer {
padding: 1rem 0;
font-size: smaller;
text-align: center;
}
</style>
5 changes: 3 additions & 2 deletions src/routes/app/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { PlantDto } from '../../lib/domain/Plant';
import { PlantDto } from '$lib/domain/Plant';
import type { ServerLoadEvent } from "@sveltejs/kit";

export async function load({ locals }) {
export async function load({ locals }: ServerLoadEvent) {
const db = locals.app.firestore();

const plantDocs = await db.collection('plants').where('owner', '==', locals.uid).get();
Expand Down
2 changes: 1 addition & 1 deletion src/routes/app/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
<div class="dashboard">
<article class="header">
<hgroup>
<h1>Hi {user.displayName},</h1>
<h1>Hi {user.displayName ?? user.email},</h1>
<h2>Take a look at your plants here!</h2>
</hgroup>
</article>
Expand Down
4 changes: 2 additions & 2 deletions src/routes/app/create-plant/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { error, fail, redirect } from '@sveltejs/kit';
import { type Actions, error, fail, redirect } from '@sveltejs/kit';
import { getAuth } from 'firebase-admin/auth';
import { FieldValue, Timestamp, getFirestore } from 'firebase-admin/firestore';
import { DateTime } from 'luxon';
Expand Down Expand Up @@ -107,7 +107,7 @@ export const actions = {

throw redirect(302, '/app');
}
};
} satisfies Actions;

function toFormattedErrors(formattedIssues: Record<string, string[]>, issue: ZodIssue) {
const field = issue.path.at(0);
Expand Down
26 changes: 23 additions & 3 deletions src/routes/login/+page.server.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,27 @@
import { redirect } from '@sveltejs/kit';
import { type Actions, redirect } from '@sveltejs/kit';
import { adminApp } from "$lib/server/firebase";

export const actions = {
default() {
async login({request, cookies}) {
const formData = await request.formData();
const idToken = formData.get('idToken');
if (!idToken) {
throw redirect(302, '/login');
}

const sessionCookie = await adminApp().auth().createSessionCookie(idToken as string, {expiresIn: 60 * 60 * 24 * 5});
cookies.set('__session', sessionCookie, {
maxAge: 60 * 60 * 24 * 5,
sameSite: 'lax',
path: '/',
secure: true,
httpOnly: true,
});

throw redirect(302, '/app');
},
logout({cookies}) {
cookies.delete('__session');
throw redirect(302, '/login');
}
};
} satisfies Actions;
Loading

0 comments on commit 0dde088

Please sign in to comment.