diff --git a/docs/docs/configuration/general-configuration.md b/docs/docs/configuration/general-configuration.md index 4ef78da0..058d1e08 100644 --- a/docs/docs/configuration/general-configuration.md +++ b/docs/docs/configuration/general-configuration.md @@ -48,48 +48,6 @@ SUPABASE_SERVICE_ROLE_KEY=your_supabase_service_role_key --- -### SUPABASE_JWT_SECRET - -#### Description - -The JWT secret for your Supabase instance. This is a required environment variable. - -#### Example - -```sh -SUPABASE_JWT_SECRET=your_supabase_jwt_secret -``` - ---- - -### NEXTAUTH_SECRET - -#### Description - -The secret used to encrypt cookies. This is a required environment variable. - -#### Example - -```sh -NEXTAUTH_SECRET=your_nextauth_secret -``` - ---- - -### NEXTAUTH_URL - -#### Description - -The base url for your app. This is a required environment variable when running in production. - -#### Example - -```sh -NEXTAUTH_URL=https://yourdomain.com -``` - ---- - ### NEXT_PUBLIC_DEFAULT_MODEL #### Description diff --git a/docs/docs/getting-started/installation.md b/docs/docs/getting-started/installation.md index 8f686d66..bc59ce4d 100644 --- a/docs/docs/getting-started/installation.md +++ b/docs/docs/getting-started/installation.md @@ -12,22 +12,15 @@ git clone https://github.com/jorge-menjivar/unSAGED.git ## Generate Supabase Tables -Run the [Generation Script](https://github.com/jorge-menjivar/unSAGED/packages/unsaged/db/GenerationScript.sql) in the [Supabase SQL editor](https://app.supabase.com/project/_/sql). +Run the database migration in [Supabase SQL editor](https://app.supabase.com/project/_/sql) or your favorite SQL editor. + This will do the following: - Create the tables required by unSAGED. -- Create the authentication schema and tables required by Auth.js. - Enable Row Level Security for the tables required by unSAGED. - Apply the Row Level Security policies required by unSAGED. -## Expose the `next_auth` schema - -Expose the `next_auth` schema in the [API settings](https://app.supabase.com/project/_/settings/api) by adding `next_auth` to the "Exposed schemas" list. - -More information [here](https://authjs.dev/reference/adapter/supabase#expose-the-nextauth-schema-in-supabase). - -Then copy the output and save it for the next step. ## Switch to the `packages/unsaged` directory @@ -42,30 +35,23 @@ cd packages/unsaged The `.env.local` file is the main configuration file for unSAGED. It should be located in the `packages/unsaged` directory of the project. Create the `packages/unsaged/.env.local` file to set your environment variables. -### Set Auth Secret - -Create your secret with the following command: - -```sh -openssl rand -base64 32 -``` -Set the `NEXTAUTH_SECRET` environment variable to the secret you just created. +### Set Supabase Variables ```sh title="packages/unsaged/.env.local" -NEXTAUTH_SECRET=my_secret +NEXT_PUBLIC_SUPABASE_URL="https://xxxxxxxxxxxxxxxx.supabase.co" +NEXT_PUBLIC_SUPABASE_ANON_KEY=supabase_anon_key ``` -See [Auth.js Documentation](https://next-auth.js.org/configuration/options#nextauth_secret) for more information. +### Configure your supabase auth providers at -### Set Supabase Variables +https://supabase.com/dashboard/project/xxxxxxxxxxxxxxxx/auth/providers -```sh title="packages/unsaged/.env.local" -NEXT_PUBLIC_SUPABASE_URL="https://xxxxxxxxxxxxxxxx.supabase.co" -NEXT_PUBLIC_SUPABASE_ANON_KEY=supabase_anon_key -SUPABASE_JWT_SECRET=supabase_jwt_secret -SUPABASE_SERVICE_ROLE_KEY=supabase_service_role_key +## Enable them or disable them on the UI by adding/removing them from ``` +/app/auth/signin/auth-form.tsx +``` + ## Install Dependencies @@ -88,14 +74,4 @@ When running in docker set the following environment variable: ```sh docker build -t unsaged . --rm docker run --env-file=.env.local -p 127.0.0.1:3000:3000 --name unsaged unsaged -``` - -## Running in Production - -To run in production, you will need to set the following environment variable. - -See [Auth.js Documentation](https://next-auth.js.org/configuration/options#nextauth_url) for more information. - -```sh title="packages/unsaged/.env.local" -NEXTAUTH_URL=https://yourdomain.com -``` +``` \ No newline at end of file diff --git a/packages/unsaged/.env.local.example b/packages/unsaged/.env.local.example index 4b7e1396..5a4ceb7f 100644 --- a/packages/unsaged/.env.local.example +++ b/packages/unsaged/.env.local.example @@ -1,11 +1,15 @@ # Will print debug messages if true NEXT_PUBLIC_DEBUG_MODE=false +# Default model NEXT_PUBLIC_DEFAULT_MODEL="gpt-3.5-turbo" +NEXT_PUBLIC_SITE_URL="http://localhost:3000" NEXT_PUBLIC_TITLE="unsaged" NEXT_PUBLIC_DESCRIPTION="Open source chat kit engineered for seamless interaction with AI models" +NEXT_PUBLIC_DEBUG_MODE=true + # OpenAI OPENAI_API_URL="https://api.openai.com/v1" OPENAI_API_KEY=openai_key @@ -52,13 +56,6 @@ PALM_API_KEY=palm_key # NEXT_PUBLIC_DEFAULT_PALM_TOP_P=1.0 # NEXT_PUBLIC_DEFAULT_PALM_TOP_K=40 -# NextAuth (Required) -NEXTAUTH_EMAIL_PATTERN= -NEXTAUTH_URL="http://localhost:3000" -# You can get secret with `openssl rand -base64 32` -NEXTAUTH_SECRET=nextauth_secret - -# At least one provider is required # Google Auth Provider GOOGLE_CLIENT_ID="xxxxxxxxxxxxxxxxx.apps.googleusercontent.com" GOOGLE_CLIENT_SECRET=google_client_secret @@ -66,5 +63,3 @@ GOOGLE_CLIENT_SECRET=google_client_secret # Supabase (Required) NEXT_PUBLIC_SUPABASE_URL="https://xxxxxxxxxxxxxxxx.supabase.co" NEXT_PUBLIC_SUPABASE_ANON_KEY=supabase_anon_key -SUPABASE_JWT_SECRET=supabase_jwt_secret -SUPABASE_SERVICE_ROLE_KEY=supabase_service_role_key diff --git a/packages/unsaged/app/account/account-form.tsx b/packages/unsaged/app/account/account-form.tsx new file mode 100644 index 00000000..689c6f2c --- /dev/null +++ b/packages/unsaged/app/account/account-form.tsx @@ -0,0 +1,129 @@ +'use client' +import { useCallback, useEffect, useState } from 'react' +import { Database } from '@/types/database' +import { Session, createClientComponentClient } from '@supabase/auth-helpers-nextjs' + +export default function AccountForm({ session }: { session: Session | null }) { + const supabase = createClientComponentClient() + const [loading, setLoading] = useState(true) + const [fullname, setFullname] = useState(null) + const [username, setUsername] = useState(null) + const [website, setWebsite] = useState(null) + const [avatar_url, setAvatarUrl] = useState(null) + const user = session?.user + + const getProfile = useCallback(async () => { + try { + setLoading(true) + + const { data, error, status } = await supabase + .from('profiles') + .select(`full_name, username, website, avatar_url`) + .eq('id', user?.id) + .single() + + if (error && status !== 406) { + throw error + } + + if (data) { + setFullname(data.full_name) + setUsername(data.username) + setWebsite(data.website) + setAvatarUrl(data.avatar_url) + } + } catch (error) { + alert('Error loading user data!') + } finally { + setLoading(false) + } + }, [user, supabase]) + + useEffect(() => { + getProfile() + }, [user, getProfile]) + + async function updateProfile({ + username, + website, + avatar_url, + }: { + username: string | null + fullname: string | null + website: string | null + avatar_url: string | null + }) { + try { + setLoading(true) + + const { error } = await supabase.from('profiles').upsert({ + id: user?.id as string, + full_name: fullname, + username, + website, + avatar_url, + updated_at: new Date().toISOString(), + }) + if (error) throw error + alert('Profile updated!') + } catch (error) { + alert('Error updating the data!') + } finally { + setLoading(false) + } + } + + return ( +
+
+ + +
+
+ + setFullname(e.target.value)} + /> +
+
+ + setUsername(e.target.value)} + /> +
+
+ + setWebsite(e.target.value)} + /> +
+ +
+ +
+ +
+
+ +
+
+
+ ) +} \ No newline at end of file diff --git a/packages/unsaged/app/account/page.tsx b/packages/unsaged/app/account/page.tsx new file mode 100644 index 00000000..0dcfe4ea --- /dev/null +++ b/packages/unsaged/app/account/page.tsx @@ -0,0 +1,14 @@ +import { createServerComponentClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { Database } from '@/types/database' +import AccountForm from './account-form' + +export default async function Account() { + const supabase = createServerComponentClient({ cookies }) + + const { + data: { session }, + } = await supabase.auth.getSession() + + return +} \ No newline at end of file diff --git a/packages/unsaged/app/api/auth/[...nextauth]/route.ts b/packages/unsaged/app/api/auth/[...nextauth]/route.ts deleted file mode 100644 index 4db1c69c..00000000 --- a/packages/unsaged/app/api/auth/[...nextauth]/route.ts +++ /dev/null @@ -1,44 +0,0 @@ -import NextAuth, { NextAuthOptions } from 'next-auth'; - -import { getProviders } from '@/utils/app/auth/providers'; -import { - SUPABASE_JWT_SECRET, - SUPABASE_SERVICE_ROLE_KEY, - SUPABASE_URL, -} from '@/utils/app/const'; - -import { SupabaseAdapter } from '@next-auth/supabase-adapter'; -import jwt from 'jsonwebtoken'; - -const authOptions: NextAuthOptions = { - providers: await getProviders(), - session: { strategy: 'jwt' }, - - // Supabase adapter is only enabled if JWT secret is specified - adapter: SUPABASE_JWT_SECRET - ? SupabaseAdapter({ - url: SUPABASE_URL, - secret: SUPABASE_SERVICE_ROLE_KEY, - }) - : undefined, - callbacks: { - async session({ session, token }) { - const signingSecret = SUPABASE_JWT_SECRET; - if (signingSecret) { - const payload = { - aud: 'authenticated', - exp: Math.floor(new Date(session.expires).getTime() / 1000), - sub: token.sub, - email: token.email, - role: 'authenticated', - }; - session.customAccessToken = jwt.sign(payload, signingSecret); - } - return session; - }, - }, -}; - -const handler = NextAuth(authOptions); - -export { handler as GET, handler as POST }; diff --git a/packages/unsaged/app/api/auth/route.ts b/packages/unsaged/app/api/auth/route.ts new file mode 100644 index 00000000..77d7c822 --- /dev/null +++ b/packages/unsaged/app/api/auth/route.ts @@ -0,0 +1,18 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextResponse } from 'next/server' + +import type { Database } from '@/types/database' + +export async function GET(request: Request) { + const requestUrl = new URL(request.url) + const code = requestUrl.searchParams.get('code') + + if (code) { + const supabase = createRouteHandlerClient({ cookies }) + await supabase.auth.exchangeCodeForSession(code) + } + + // URL to redirect to after sign in process completes + return NextResponse.redirect(requestUrl.origin) +} diff --git a/packages/unsaged/app/auth/callback/route.ts b/packages/unsaged/app/auth/callback/route.ts new file mode 100644 index 00000000..4f73ce28 --- /dev/null +++ b/packages/unsaged/app/auth/callback/route.ts @@ -0,0 +1,16 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { NextRequest, NextResponse } from 'next/server' + +export async function GET(req: NextRequest) { + const cookieStore = cookies() + const supabase = createRouteHandlerClient({ cookies: () => cookieStore }) + const { searchParams } = new URL(req.url) + const code = searchParams.get('code') + + if (code) { + await supabase.auth.exchangeCodeForSession(code) + } + + return NextResponse.redirect(new URL('/', req.url)) +} \ No newline at end of file diff --git a/packages/unsaged/app/auth/signin/auth-form.tsx b/packages/unsaged/app/auth/signin/auth-form.tsx new file mode 100644 index 00000000..cebf91bc --- /dev/null +++ b/packages/unsaged/app/auth/signin/auth-form.tsx @@ -0,0 +1,22 @@ +'use client' +import { Auth } from '@supabase/auth-ui-react' +import { ThemeSupa } from '@supabase/auth-ui-shared' +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' +import { Database } from '@/types/database' + +export default function AuthForm() { + const supabase = createClientComponentClient() + const providersFromEnv = process.env.AUTH_PROVIDERS ? process.env.AUTH_PROVIDERS.split(',') : []; + + return ( + + ) +} diff --git a/packages/unsaged/app/auth/signin/page.tsx b/packages/unsaged/app/auth/signin/page.tsx new file mode 100644 index 00000000..e99ddc17 --- /dev/null +++ b/packages/unsaged/app/auth/signin/page.tsx @@ -0,0 +1,20 @@ +import AuthForm from './auth-form'; + +export default function Home() { + // Access the title from the environment variables + const title = process.env.NEXT_PUBLIC_TITLE || 'Default Title'; + + return ( +
+
+

{title}

+

+ Please sign in to your account +

+
+ +
+
+
+ ); +} diff --git a/packages/unsaged/app/auth/signout/route.ts b/packages/unsaged/app/auth/signout/route.ts new file mode 100644 index 00000000..2269b267 --- /dev/null +++ b/packages/unsaged/app/auth/signout/route.ts @@ -0,0 +1,21 @@ +import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs' +import { cookies } from 'next/headers' +import { type NextRequest, NextResponse } from 'next/server' + +export async function POST(req: NextRequest) { + const cookieStore = cookies() + const supabase = createRouteHandlerClient({ cookies: () => cookieStore }) + + // Check if we have a session + const { + data: { session }, + } = await supabase.auth.getSession() + + if (session) { + await supabase.auth.signOut() + } + + return NextResponse.redirect(new URL('/', req.url), { + status: 302, + }) +} \ No newline at end of file diff --git a/packages/unsaged/components/Home/components/PrimaryMenu/components/ActivityBar/ActivityBar.tsx b/packages/unsaged/components/Home/components/PrimaryMenu/components/ActivityBar/ActivityBar.tsx index dbd4ac15..4b58b5fd 100644 --- a/packages/unsaged/components/Home/components/PrimaryMenu/components/ActivityBar/ActivityBar.tsx +++ b/packages/unsaged/components/Home/components/PrimaryMenu/components/ActivityBar/ActivityBar.tsx @@ -4,9 +4,9 @@ import { IconLogout, IconSettings, } from '@tabler/icons-react'; -import { signOut } from 'next-auth/react'; import { useContext } from 'react'; + import { localSaveShowPrimaryMenu } from '@/utils/app/storage/local/ui-state'; import { deleteSelectedConversationId } from '@/utils/app/storage/selectedConversation'; @@ -16,6 +16,11 @@ import HomeContext from '@/components/Home/home.context'; import PrimaryMenuContext from '../../PrimaryMenu.context'; +import type { Database } from '@/types/database' +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs' +import { useRouter } from 'next/navigation' +const supabase = createClientComponentClient() + const ActivityBar = ({ icons }: { icons: JSX.Element[] }) => { const { state: { user, database, showPrimaryMenu, display }, @@ -40,14 +45,15 @@ const ActivityBar = ({ icons }: { icons: JSX.Element[] }) => { primaryMenuDispatch({ field: 'selectedIndex', value: index }); }; - const handleSignOut = () => { + const handleSignOut = async () => { if (database!.name !== 'local') { - deleteSelectedConversationId(user!); + await deleteSelectedConversationId(user!); } - - signOut(); + + await supabase.auth.signOut() + window.location.replace('/'); }; - + const handleShowSettings = () => { if (display !== 'settings') { homeDispatch({ field: 'display', value: 'settings' }); diff --git a/packages/unsaged/db/GenerationScript.sql b/packages/unsaged/db/GenerationScript.sql index b862df7c..16fe7064 100644 --- a/packages/unsaged/db/GenerationScript.sql +++ b/packages/unsaged/db/GenerationScript.sql @@ -1,105 +1,3 @@ --- --- Name: next_auth; Type: SCHEMA; --- -CREATE SCHEMA next_auth; - -GRANT USAGE ON SCHEMA next_auth TO service_role; -GRANT ALL ON SCHEMA next_auth TO postgres; - --- --- Create users table --- -CREATE TABLE IF NOT EXISTS next_auth.users -( - id uuid NOT NULL DEFAULT uuid_generate_v4(), - name text, - email text, - "emailVerified" timestamp with time zone, - image text, - CONSTRAINT users_pkey PRIMARY KEY (id), - CONSTRAINT email_unique UNIQUE (email) -); - -GRANT ALL ON TABLE next_auth.users TO postgres; -GRANT ALL ON TABLE next_auth.users TO service_role; - ---- uid() function to be used in RLS policies -CREATE FUNCTION next_auth.uid() RETURNS uuid - LANGUAGE sql STABLE - AS $$ - select - coalesce( - nullif(current_setting('request.jwt.claim.sub', true), ''), - (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub') - )::uuid -$$; - --- --- Create sessions table --- -CREATE TABLE IF NOT EXISTS next_auth.sessions -( - id uuid NOT NULL DEFAULT uuid_generate_v4(), - expires timestamp with time zone NOT NULL, - "sessionToken" text NOT NULL, - "userId" uuid, - CONSTRAINT sessions_pkey PRIMARY KEY (id), - CONSTRAINT sessionToken_unique UNIQUE ("sessionToken"), - CONSTRAINT "sessions_userId_fkey" FOREIGN KEY ("userId") - REFERENCES next_auth.users (id) MATCH SIMPLE - ON UPDATE NO ACTION - ON DELETE CASCADE -); - -GRANT ALL ON TABLE next_auth.sessions TO postgres; -GRANT ALL ON TABLE next_auth.sessions TO service_role; - --- --- Create accounts table --- -CREATE TABLE IF NOT EXISTS next_auth.accounts -( - id uuid NOT NULL DEFAULT uuid_generate_v4(), - type text NOT NULL, - provider text NOT NULL, - "providerAccountId" text NOT NULL, - refresh_token text, - access_token text, - expires_at bigint, - token_type text, - scope text, - id_token text, - session_state text, - oauth_token_secret text, - oauth_token text, - "userId" uuid, - CONSTRAINT accounts_pkey PRIMARY KEY (id), - CONSTRAINT provider_unique UNIQUE (provider, "providerAccountId"), - CONSTRAINT "accounts_userId_fkey" FOREIGN KEY ("userId") - REFERENCES next_auth.users (id) MATCH SIMPLE - ON UPDATE NO ACTION - ON DELETE CASCADE -); - -GRANT ALL ON TABLE next_auth.accounts TO postgres; -GRANT ALL ON TABLE next_auth.accounts TO service_role; - --- --- Create verification_tokens table --- -CREATE TABLE IF NOT EXISTS next_auth.verification_tokens -( - identifier text, - token text, - expires timestamp with time zone NOT NULL, - CONSTRAINT verification_tokens_pkey PRIMARY KEY (token), - CONSTRAINT token_unique UNIQUE (token), - CONSTRAINT token_identifier_unique UNIQUE (token, identifier) -); - -GRANT ALL ON TABLE next_auth.verification_tokens TO postgres; -GRANT ALL ON TABLE next_auth.verification_tokens TO service_role; - -- public.conversations definition @@ -115,7 +13,7 @@ CREATE TABLE public.conversations ( temperature float4 NULL, folder_id uuid NULL, "timestamp" timestamptz NOT NULL, - user_id uuid NOT NULL DEFAULT next_auth.uid(), + user_id uuid NOT NULL DEFAULT auth.uid(), params jsonb NOT NULL DEFAULT '{}'::jsonb, CONSTRAINT unique_conversation_id PRIMARY KEY (id) ); @@ -131,7 +29,7 @@ CREATE TABLE public.folders ( id uuid NOT NULL DEFAULT uuid_generate_v4(), "name" text NOT NULL, folder_type text NOT NULL, - user_id uuid NOT NULL DEFAULT next_auth.uid(), + user_id uuid NOT NULL DEFAULT auth.uid(), CONSTRAINT unique_folder_id PRIMARY KEY (id) ); @@ -148,7 +46,7 @@ CREATE TABLE public.messages ( "content" text NOT NULL, "timestamp" timestamptz NOT NULL, conversation_id uuid NOT NULL, - user_id uuid NOT NULL DEFAULT next_auth.uid(), + user_id uuid NOT NULL DEFAULT auth.uid(), CONSTRAINT unique_message_id PRIMARY KEY (id) ); @@ -166,7 +64,7 @@ CREATE TABLE public.prompts ( "content" text NOT NULL, models _text NOT NULL, folder_id uuid NULL, - user_id uuid NOT NULL DEFAULT next_auth.uid(), + user_id uuid NOT NULL DEFAULT auth.uid(), CONSTRAINT unique_prompt_id PRIMARY KEY (id) ); @@ -183,37 +81,37 @@ CREATE TABLE public.system_prompts ( "content" text NOT NULL, models _text NOT NULL, folder_id uuid NULL, - user_id uuid NOT NULL DEFAULT next_auth.uid(), + user_id uuid NOT NULL DEFAULT auth.uid(), CONSTRAINT unique_system_prompt_id PRIMARY KEY (id) ); -- public.conversations foreign keys -ALTER TABLE public.conversations ADD CONSTRAINT conversation_owner FOREIGN KEY (user_id) REFERENCES next_auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE public.conversations ADD CONSTRAINT conversation_owner FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE public.conversations ADD CONSTRAINT conversation_owner_folder FOREIGN KEY (folder_id) REFERENCES public.folders(id) ON UPDATE CASCADE; -- public.folders foreign keys -ALTER TABLE public.folders ADD CONSTRAINT folder_owner FOREIGN KEY (user_id) REFERENCES next_auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE public.folders ADD CONSTRAINT folder_owner FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; -- public.messages foreign keys -ALTER TABLE public.messages ADD CONSTRAINT message_owner FOREIGN KEY (user_id) REFERENCES next_auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE public.messages ADD CONSTRAINT message_owner FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE public.messages ADD CONSTRAINT message_owner_convo FOREIGN KEY (conversation_id) REFERENCES public.conversations(id) ON DELETE CASCADE ON UPDATE CASCADE; -- public.prompts foreign keys -ALTER TABLE public.prompts ADD CONSTRAINT prompt_owner FOREIGN KEY (user_id) REFERENCES next_auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE public.prompts ADD CONSTRAINT prompt_owner FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE public.prompts ADD CONSTRAINT prompt_owner_folder FOREIGN KEY (folder_id) REFERENCES public.folders(id) ON UPDATE CASCADE; -- public.system_prompts foreign keys -ALTER TABLE public.system_prompts ADD CONSTRAINT system_prompt_owner FOREIGN KEY (user_id) REFERENCES next_auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; +ALTER TABLE public.system_prompts ADD CONSTRAINT system_prompt_owner FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE ON UPDATE CASCADE; ALTER TABLE public.system_prompts ADD CONSTRAINT system_prompt_owner_folder FOREIGN KEY (folder_id) REFERENCES public.folders(id) ON UPDATE CASCADE; @@ -241,18 +139,18 @@ CREATE POLICY "Enable insert for authenticated users only" ON "public"."folders" CREATE POLICY "Enable select for owners" ON "public"."folders" AS PERMISSIVE FOR SELECT TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); CREATE POLICY "Enable update for owners" ON "public"."folders" AS PERMISSIVE FOR UPDATE TO public - USING (next_auth.uid() = user_id) - WITH CHECK (next_auth.uid() = user_id); + USING (auth.uid() = user_id) + WITH CHECK (auth.uid() = user_id); CREATE POLICY "Enable delete for owners" ON "public"."folders" AS PERMISSIVE FOR DELETE TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); @@ -265,18 +163,18 @@ CREATE POLICY "Enable insert for authenticated users only" ON "public"."conversa CREATE POLICY "Enable select for owners" ON "public"."conversations" AS PERMISSIVE FOR SELECT TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); CREATE POLICY "Enable update for owners" ON "public"."conversations" AS PERMISSIVE FOR UPDATE TO public - USING (next_auth.uid() = user_id) - WITH CHECK (next_auth.uid() = user_id); + USING (auth.uid() = user_id) + WITH CHECK (auth.uid() = user_id); CREATE POLICY "Enable delete for owners" ON "public"."conversations" AS PERMISSIVE FOR DELETE TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); @@ -289,18 +187,18 @@ CREATE POLICY "Enable insert for authenticated users only" ON "public"."prompts" CREATE POLICY "Enable select for owners" ON "public"."prompts" AS PERMISSIVE FOR SELECT TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); CREATE POLICY "Enable update for owners" ON "public"."prompts" AS PERMISSIVE FOR UPDATE TO public - USING (next_auth.uid() = user_id) - WITH CHECK (next_auth.uid() = user_id); + USING (auth.uid() = user_id) + WITH CHECK (auth.uid() = user_id); CREATE POLICY "Enable delete for owners" ON "public"."prompts" AS PERMISSIVE FOR DELETE TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); @@ -313,18 +211,18 @@ CREATE POLICY "Enable insert for authenticated users only" ON "public"."messages CREATE POLICY "Enable select for owners" ON "public"."messages" AS PERMISSIVE FOR SELECT TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); CREATE POLICY "Enable update for owners" ON "public"."messages" AS PERMISSIVE FOR UPDATE TO public - USING (next_auth.uid() = user_id) - WITH CHECK (next_auth.uid() = user_id); + USING (auth.uid() = user_id) + WITH CHECK (auth.uid() = user_id); CREATE POLICY "Enable delete for owners" ON "public"."messages" AS PERMISSIVE FOR DELETE TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); @@ -337,16 +235,16 @@ CREATE POLICY "Enable insert for authenticated users only" ON "public"."system_p CREATE POLICY "Enable select for owners" ON "public"."system_prompts" AS PERMISSIVE FOR SELECT TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); CREATE POLICY "Enable update for owners" ON "public"."system_prompts" AS PERMISSIVE FOR UPDATE TO public - USING (next_auth.uid() = user_id) - WITH CHECK (next_auth.uid() = user_id); + USING (auth.uid() = user_id) + WITH CHECK (auth.uid() = user_id); CREATE POLICY "Enable delete for owners" ON "public"."system_prompts" AS PERMISSIVE FOR DELETE TO public - USING (next_auth.uid() = user_id); + USING (auth.uid() = user_id); diff --git a/packages/unsaged/middleware.ts b/packages/unsaged/middleware.ts index 73c90549..9b3125f0 100644 --- a/packages/unsaged/middleware.ts +++ b/packages/unsaged/middleware.ts @@ -1,43 +1,29 @@ -import { withAuth } from 'next-auth/middleware'; -import { get } from '@vercel/edge-config'; +import { createMiddlewareClient } from '@supabase/auth-helpers-nextjs' +import { NextResponse } from 'next/server' -import { dockerEnvVarFix } from './utils/app/docker/envFix'; +import type { NextRequest } from 'next/server' -const getSecret = () => { - return dockerEnvVarFix(process.env.NEXTAUTH_SECRET); -}; +export async function middleware(req: NextRequest) { + const res = NextResponse.next() + const supabase = createMiddlewareClient({ req, res }) -const getEmailPatterns = async () => { - let patternsString = dockerEnvVarFix(process.env.NEXTAUTH_EMAIL_PATTERNS); + const { + data: { user }, + } = await supabase.auth.getUser() - if (dockerEnvVarFix(process.env.EDGE_CONFIG)) - patternsString = await get('NEXTAUTH_EMAIL_PATTERNS'); + // if user is signed in and the current path is / redirect the user to /account + if (user && req.nextUrl.pathname !== '/') { + return NextResponse.redirect(new URL('/', req.url)) + } - return patternsString ? patternsString.split(',') : []; -}; + // if user is not signed in and the current path is not / redirect the user to / + if (!user && req.nextUrl.pathname !== '/auth/signin') { + return NextResponse.redirect(new URL('/auth/signin', req.url)) + } -export default withAuth({ - callbacks: { - async authorized({ token }) { - if (!token?.email) { - return false; - } else { - const patterns = await getEmailPatterns(); - if (patterns.length === 0) { - return true; // No patterns specified, allow access - } + return res +} - const email = token.email.toLowerCase(); - for (const pattern of patterns) { - const regex = new RegExp(pattern.trim()); - if (email.match(regex)) { - return true; // Email matches one of the patterns, allow access - } - } - - return false; // Email does not match any of the patterns, deny access - } - }, - }, - secret: getSecret(), -}); +export const config = { + matcher: ['/','/auth/signin', '/account'], +} \ No newline at end of file diff --git a/packages/unsaged/package.json b/packages/unsaged/package.json index 95c018cc..d520a630 100644 --- a/packages/unsaged/package.json +++ b/packages/unsaged/package.json @@ -14,15 +14,16 @@ "dependencies": { "@auth/supabase-adapter": "^0.1.7", "@dqbd/tiktoken": "^1.0.7", - "@next-auth/supabase-adapter": "^0.2.1", "@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slider": "^1.1.2", "@radix-ui/react-switch": "^1.0.3", "@radix-ui/react-tooltip": "^1.0.7", - "@supabase/auth-helpers-nextjs": "^0.6.1", - "@supabase/supabase-js": "^2.38.4", + "@supabase/auth-helpers-nextjs": "^0.8.7", + "@supabase/auth-ui-react": "^0.4.6", + "@supabase/auth-ui-shared": "^0.1.8", + "@supabase/supabase-js": "^2.38.5", "@tabler/icons-react": "^2.40.0", "@vercel/analytics": "^1.1.1", "@vercel/edge-config": "^0.4.1", @@ -35,7 +36,6 @@ "js-yaml": "^4.1.0", "jsonwebtoken": "^9.0.2", "next": "^13.5.6", - "next-auth": "^4.24.5", "next-axiom": "^1.1.0", "next-i18next": "^13.3.0", "next-themes": "^0.2.1", diff --git a/packages/unsaged/types/next-auth.d.ts b/packages/unsaged/types/next-auth.d.ts deleted file mode 100644 index 1b5c8e49..00000000 --- a/packages/unsaged/types/next-auth.d.ts +++ /dev/null @@ -1,10 +0,0 @@ -import NextAuth, { DefaultSession } from 'next-auth'; - -declare module 'next-auth' { - // Returned by `useSession`, `getSession` and received as a prop on the `SessionProvider` React Context - interface Session { - // A JWT which can be used as Authorization header with supabase-js for RLS. - customAccessToken: string; - expiresAt: number; - } -} diff --git a/packages/unsaged/utils/app/auth/helpers.ts b/packages/unsaged/utils/app/auth/helpers.ts index 8f52313c..39d22242 100644 --- a/packages/unsaged/utils/app/auth/helpers.ts +++ b/packages/unsaged/utils/app/auth/helpers.ts @@ -1,44 +1,58 @@ -import { getSession } from 'next-auth/react'; +import { useEffect, useState } from 'react'; +import { createClientComponentClient } from '@supabase/auth-helpers-nextjs'; -import { Session, User } from '@/types/auth'; +// Assuming you have initialized your Supabase client somewhere in your code +const supabase = createClientComponentClient(); -export const getClientSession = async () => { - const authjsSession = await getSession(); +export async function getClientSession() { + try { + const { data: sessionWrapper } = await supabase.auth.getSession(); + const session = sessionWrapper?.session; - if (authjsSession) { - let user: User | undefined = undefined; - if (authjsSession.user) { - user = { - email: authjsSession?.user?.email, - name: authjsSession?.user?.name, - image: authjsSession?.user?.image, - }; + if (!session) { + return null; } - const session: Session = { - user: user, - expires: authjsSession.expires, - customAccessToken: authjsSession.customAccessToken, + const sessionData = { + user: session.user?.email || '', + expires: session.expires_at || '', + customAccessToken: session.access_token || '', }; - return session; + return sessionData; + } catch (error) { + // Handle the error here, e.g., log it or return an error response + console.error(error); + return null; } +} - return null; -}; +export async function getUser() { + try { + const { data: sessionWrapper } = await supabase.auth.getSession(); + const session = sessionWrapper?.session; -export const getUser = async () => { - const session = await getClientSession(); + let user; - let user = session?.user; + if (session?.user) { + const { email, user_metadata } = session.user; + user = { + email: email || 'default_user', + image: '', + name: user_metadata?.full_name || 'Default User', + }; + } else { + user = { + email: 'default_user', + image: '', + name: 'Default User', + }; + } - if (!user) { - user = { - email: 'default_user', - image: null, - name: 'Default User', - }; + return user; + } catch (error) { + // Handle the error here, e.g., log it or return an error response + console.error(error); + return null; } - - return user; -}; +} diff --git a/packages/unsaged/utils/app/auth/providers.ts b/packages/unsaged/utils/app/auth/providers.ts deleted file mode 100644 index 8d60ae81..00000000 --- a/packages/unsaged/utils/app/auth/providers.ts +++ /dev/null @@ -1,244 +0,0 @@ -import { - APPLE_CLIENT_ID, - APPLE_CLIENT_SECRET, - AUTH0_CLIENT_ID, - AUTH0_CLIENT_SECRET, - AUTH0_ISSUER, - COGNITO_CLIENT_ID, - COGNITO_CLIENT_SECRET, - DISCORD_CLIENT_ID, - DISCORD_CLIENT_SECRET, - EMAIL_FROM, - EMAIL_SERVER, - FACEBOOK_CLIENT_ID, - FACEBOOK_CLIENT_SECRET, - GITHUB_CLIENT_ID, - GITHUB_CLIENT_SECRET, - GITLAB_CLIENT_ID, - GITLAB_CLIENT_SECRET, - GOOGLE_CLIENT_ID, - GOOGLE_CLIENT_SECRET, - OKTA_CLIENT_ID, - OKTA_CLIENT_SECRET, - REDDIT_CLIENT_ID, - REDDIT_CLIENT_SECRET, - SALESFORCE_CLIENT_ID, - SALESFORCE_CLIENT_SECRET, - SLACK_CLIENT_ID, - SLACK_CLIENT_SECRET, - SPOTIFY_CLIENT_ID, - SPOTIFY_CLIENT_SECRET, - TWITCH_CLIENT_ID, - TWITCH_CLIENT_SECRET, - TWITTER_CLIENT_ID, - TWITTER_CLIENT_SECRET, -} from './constants'; - -const authorization = { - params: { - prompt: 'consent', - access_type: 'offline', - response_type: 'code', - }, -}; - -export async function getProviders() { - const providers = []; - if (APPLE_CLIENT_ID) { - const provider = await import('next-auth/providers/apple'); - const AppleProvider = provider.default; - providers.push( - AppleProvider({ - clientId: APPLE_CLIENT_ID!, - clientSecret: APPLE_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (AUTH0_CLIENT_ID) { - const provider = await import('next-auth/providers/auth0'); - const Auth0Provider = provider.default; - providers.push( - Auth0Provider({ - clientId: AUTH0_CLIENT_ID!, - clientSecret: AUTH0_CLIENT_SECRET!, - issuer: AUTH0_ISSUER, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (COGNITO_CLIENT_ID) { - const provider = await import('next-auth/providers/cognito'); - const CognitoProvider = provider.default; - providers.push( - CognitoProvider({ - clientId: COGNITO_CLIENT_ID!, - clientSecret: COGNITO_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (DISCORD_CLIENT_ID) { - const provider = await import('next-auth/providers/discord'); - const DiscordProvider = provider.default; - providers.push( - DiscordProvider({ - clientId: DISCORD_CLIENT_ID!, - clientSecret: DISCORD_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (EMAIL_FROM) { - const provider = await import('next-auth/providers/email'); - const EmailProvider = provider.default; - providers.push( - EmailProvider({ - server: EMAIL_SERVER, - from: EMAIL_FROM, - }), - ); - } - if (FACEBOOK_CLIENT_ID) { - const provider = await import('next-auth/providers/facebook'); - const FacebookProvider = provider.default; - providers.push( - FacebookProvider({ - clientId: FACEBOOK_CLIENT_ID!, - clientSecret: FACEBOOK_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (GITHUB_CLIENT_ID) { - const provider = await import('next-auth/providers/github'); - const GithubProvider = provider.default; - providers.push( - GithubProvider({ - clientId: GITHUB_CLIENT_ID!, - clientSecret: GITHUB_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (GITLAB_CLIENT_ID) { - const provider = await import('next-auth/providers/gitlab'); - const GitlabProvider = provider.default; - providers.push( - GitlabProvider({ - clientId: GITLAB_CLIENT_ID!, - clientSecret: GITLAB_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (GOOGLE_CLIENT_ID) { - const provider = await import('next-auth/providers/google'); - const GoogleProvider = provider.default; - providers.push( - GoogleProvider({ - clientId: GOOGLE_CLIENT_ID!, - clientSecret: GOOGLE_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (OKTA_CLIENT_ID) { - const provider = await import('next-auth/providers/okta'); - const OktaProvider = provider.default; - providers.push( - OktaProvider({ - clientId: OKTA_CLIENT_ID!, - clientSecret: OKTA_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (REDDIT_CLIENT_ID) { - const provider = await import('next-auth/providers/reddit'); - const RedditProvider = provider.default; - providers.push( - RedditProvider({ - clientId: REDDIT_CLIENT_ID!, - clientSecret: REDDIT_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - if (SALESFORCE_CLIENT_ID) { - const provider = await import('next-auth/providers/salesforce'); - const SalesforceProvider = provider.default; - providers.push( - SalesforceProvider({ - clientId: SALESFORCE_CLIENT_ID!, - clientSecret: SALESFORCE_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - - if (SLACK_CLIENT_ID) { - const provider = await import('next-auth/providers/slack'); - const SlackProvider = provider.default; - providers.push( - SlackProvider({ - clientId: SLACK_CLIENT_ID!, - clientSecret: SLACK_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - - if (SPOTIFY_CLIENT_ID) { - const provider = await import('next-auth/providers/spotify'); - const SpotifyProvider = provider.default; - providers.push( - SpotifyProvider({ - clientId: SPOTIFY_CLIENT_ID!, - clientSecret: SPOTIFY_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - - if (TWITCH_CLIENT_ID) { - const provider = await import('next-auth/providers/twitch'); - const TwitchProvider = provider.default; - providers.push( - TwitchProvider({ - clientId: TWITCH_CLIENT_ID!, - clientSecret: TWITCH_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - - if (TWITTER_CLIENT_ID) { - const provider = await import('next-auth/providers/twitter'); - const TwitterProvider = provider.default; - providers.push( - TwitterProvider({ - clientId: TWITTER_CLIENT_ID!, - clientSecret: TWITTER_CLIENT_SECRET!, - authorization: authorization, - allowDangerousEmailAccountLinking: true, - }), - ); - } - - return providers; -} diff --git a/packages/unsaged/utils/app/auth/supabaseClient.ts b/packages/unsaged/utils/app/auth/supabaseClient.ts new file mode 100644 index 00000000..9ef1b61f --- /dev/null +++ b/packages/unsaged/utils/app/auth/supabaseClient.ts @@ -0,0 +1,10 @@ +import { createClient } from '@supabase/supabase-js'; + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; +const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + +if (!supabaseUrl || !supabaseAnonKey) { + throw new Error("Supabase URL or Anonymous Key is undefined"); +} + +export const supabase = createClient(supabaseUrl, supabaseAnonKey);