From d186ffb6d716e3804570e66218bcc78c07217c45 Mon Sep 17 00:00:00 2001 From: Michael Brusegard <56915010+michaelbrusegard@users.noreply.github.com> Date: Sun, 17 Nov 2024 15:48:32 +0100 Subject: [PATCH] fix: ability to set cookies in trpc --- src/app/[locale]/auth/page.tsx | 14 +----- src/app/api/auth/feide/route.ts | 57 ++++++++++++----------- src/app/api/data/[trpc]/route.ts | 5 +- src/components/auth/FeideButton.tsx | 25 ++++++++++ src/components/providers/TRPCProvider.tsx | 9 +--- src/server/api/routers/auth.ts | 30 ++++++++++-- src/server/auth/feide.ts | 20 ++++---- 7 files changed, 99 insertions(+), 61 deletions(-) create mode 100644 src/components/auth/FeideButton.tsx diff --git a/src/app/[locale]/auth/page.tsx b/src/app/[locale]/auth/page.tsx index dd2b79a..f46d61e 100644 --- a/src/app/[locale]/auth/page.tsx +++ b/src/app/[locale]/auth/page.tsx @@ -1,11 +1,9 @@ -import { FeideLogo } from '@/components/assets/logos/FeideLogo'; +import { FeideButton } from '@/components/auth/FeideButton'; import { Button } from '@/components/ui/Button'; import { Separator } from '@/components/ui/Separator'; -import { api } from '@/lib/api/server'; import { Link } from '@/lib/locale/navigation'; import { FingerprintIcon } from 'lucide-react'; import { getTranslations, setRequestLocale } from 'next-intl/server'; -import ExternalLink from 'next/link'; export default async function SignInPage({ params, @@ -15,7 +13,6 @@ export default async function SignInPage({ const { locale } = await params; setRequestLocale(locale); const t = await getTranslations('auth'); - const feideUrlHref = await api.auth.getFeideUrlHref(); return (
@@ -25,14 +22,7 @@ export default async function SignInPage({

{t('signInWith')}

- + + ); +} + +export { FeideButton }; diff --git a/src/components/providers/TRPCProvider.tsx b/src/components/providers/TRPCProvider.tsx index f7761f1..4011ee4 100644 --- a/src/components/providers/TRPCProvider.tsx +++ b/src/components/providers/TRPCProvider.tsx @@ -4,7 +4,7 @@ import { env } from '@/env'; import { api } from '@/lib/api/client'; import { createQueryClient } from '@/lib/api/queryClient'; import { type QueryClient, QueryClientProvider } from '@tanstack/react-query'; -import { loggerLink, unstable_httpBatchStreamLink } from '@trpc/client'; +import { httpBatchLink, loggerLink } from '@trpc/client'; import { useState } from 'react'; import SuperJSON from 'superjson'; @@ -32,14 +32,9 @@ function TRPCProvider(props: { children: React.ReactNode }) { process.env.NODE_ENV === 'development' || (op.direction === 'down' && op.result instanceof Error), }), - unstable_httpBatchStreamLink({ + httpBatchLink({ transformer: SuperJSON, url: `${env.NEXT_PUBLIC_SITE_URL}/api/data`, - headers: () => { - const headers = new Headers(); - headers.set('x-trpc-source', 'nextjs-react'); - return headers; - }, }), ], }), diff --git a/src/server/api/routers/auth.ts b/src/server/api/routers/auth.ts index f326d5a..a6a0f10 100644 --- a/src/server/api/routers/auth.ts +++ b/src/server/api/routers/auth.ts @@ -1,15 +1,16 @@ +import { env } from '@/env'; import { publicProcedure } from '@/server/api/procedures'; import { RefillingTokenBucket } from '@/server/api/rate-limit/refillingTokenBucket'; import { createRouter } from '@/server/api/trpc'; -import { getFeideAuthorizationUrl } from '@/server/auth/feide'; +import { createFeideAuthorization } from '@/server/auth/feide'; import { TRPCError } from '@trpc/server'; -import { headers } from 'next/headers'; +import { cookies, headers } from 'next/headers'; const ipBucket = new RefillingTokenBucket(5, 60); const authRouter = createRouter({ - getFeideUrlHref: publicProcedure.query(async () => { + signInFeide: publicProcedure.mutation(async () => { const headerStore = await headers(); const clientIP = headerStore.get('X-Forwarded-For'); @@ -19,8 +20,27 @@ const authRouter = createRouter({ message: 'Rate limit exceeded. Please try again later.', }); } - const feideUrl = await getFeideAuthorizationUrl(); - return feideUrl.href; + + const cookieStore = await cookies(); + const { state, codeVerifier, url } = await createFeideAuthorization(); + cookieStore.set('feide-state', state, { + path: '/', + httpOnly: true, + sameSite: 'lax', + maxAge: 60 * 10, + secure: env.NODE_ENV === 'production', + }); + cookieStore.set('feide-code-verifier', codeVerifier, { + path: '/', + httpOnly: true, + sameSite: 'lax', + maxAge: 60 * 10, + secure: env.NODE_ENV === 'production', + }); + + console.log(cookieStore.getAll()); + + return url.href; }), }); diff --git a/src/server/auth/feide.ts b/src/server/auth/feide.ts index a7d76d7..b4fa2cc 100644 --- a/src/server/auth/feide.ts +++ b/src/server/auth/feide.ts @@ -15,21 +15,22 @@ const feideOAuthClient = new OAuth2Client( }, ); -function getFeideAuthorizationUrl() { +async function createFeideAuthorization() { const state = generateState(); - const codeVerifier = generateCodeVerifier(); // Optional for PKCE flow - - return feideOAuthClient.createAuthorizationURL({ + const codeVerifier = generateCodeVerifier(); + const url = await feideOAuthClient.createAuthorizationURL({ state, scopes: ['openid', 'profile', 'email'], codeVerifier, }); + return { + state, + codeVerifier, + url, + }; } -async function validateFeideAuthorizationCode( - code: string, - codeVerifier: string, -) { +async function validateFeideAuthorization(code: string, codeVerifier: string) { try { const tokens = await feideOAuthClient.validateAuthorizationCode(code, { codeVerifier, @@ -43,9 +44,10 @@ async function validateFeideAuthorizationCode( } catch (error) { if (error instanceof OAuth2RequestError) { // probably invalid credentials etc + // will be handled by returning null } console.error(error); } } -export { getFeideAuthorizationUrl, validateFeideAuthorizationCode }; +export { createFeideAuthorization, validateFeideAuthorization };