Skip to content

Commit

Permalink
Merge pull request #63 from diggerhq/sp-sso-support
Browse files Browse the repository at this point in the history
supabase auth sso support
  • Loading branch information
ZIJ authored Oct 17, 2024
2 parents ee984ff + cd70318 commit 0df1aa6
Show file tree
Hide file tree
Showing 10 changed files with 405 additions and 219 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ async function getDefaultOrganizationOrSet(): Promise<string | null> {
// reason, because of an invite or for some other reason,
// make sure that the default organization is set to the first
await setDefaultOrganization(firstOrganization.id);
console.log('set default organization to firstOrganization on onboarding', firstOrganization.id);

return firstOrganization.id;
}

Expand All @@ -39,9 +37,6 @@ async function getOnboardingConditions(userId: string) {
getUserProfile(userId),
getDefaultOrganizationOrSet(),
]);

console.log('userProfile on onboarding', userProfile);
console.log('defaultOrganizationId on onboarding', defaultOrganizationId);
return {
userProfile,
defaultOrganizationId,
Expand All @@ -54,10 +49,7 @@ async function OnboardingFlowWrapper({ userId, userEmail }: { userId: string; us
serverGetLoggedInUser(),
]);
const { userProfile } = onboardingConditions;
console.log("userProfile", userProfile);
const onboardingStatus = authUserMetadataSchema.parse(user.user_metadata);
console.log(onboardingStatus);
console.log(userEmail);

return (
<UserOnboardingFlow
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import {
createDefaultUserPrivateInfo,
createDefaultUserProfile,
} from '@/data/user/user';
import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs';
import { cookies } from 'next/headers';
import { NextResponse } from 'next/server';

const isDevelopment = process.env.NODE_ENV === 'development';

export async function GET(request: Request) {
const requestUrl = new URL(request.url);
const access_token = requestUrl.searchParams.get('access_token');
const refresh_token = requestUrl.searchParams.get('refresh_token');
const next = requestUrl.searchParams.get('next');
const provider = requestUrl.searchParams.get('provider');
const supabase = createRouteHandlerClient({ cookies });

if (access_token && refresh_token) {
try {
const { data, error } = await supabase.auth.setSession({
access_token,
refresh_token,
});

// TODO: find out how user profile and private info are created automatically
const userId = data.user?.id;
createDefaultUserProfile(userId!);
createDefaultUserPrivateInfo(userId!);

console.log('Session set successfully:', data.session);
} catch (error) {
console.error('Error setting session:', error);
}
}

let redirectTo = new URL('/dashboard', requestUrl.origin);

if (next) {
// decode next param
const decodedNext = decodeURIComponent(next);
// validate next param
redirectTo = new URL(decodedNext, requestUrl.origin);
}

return NextResponse.redirect(redirectTo);
}
31 changes: 31 additions & 0 deletions src/app/(dynamic-pages)/(login-pages)/auth/sso-verify/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@

'use client'

import { supabaseAnonClient } from '@/supabase-clients/anon/supabaseAnonClient';
import { AuthChangeEvent, Session } from "@supabase/supabase-js";
import { useEffect, useState } from "react";


export default function Default() {
const supabase = supabaseAnonClient;

const [authState, setAuthState] = useState<AuthChangeEvent | "">("");
const [session, setSession] = useState<Session | null>(null);

useEffect(() => {
supabase.auth.onAuthStateChange((event, sessionValue) => {
console.log('state change', event, sessionValue)
if (event === "INITIAL_SESSION") {
window.location.href = `/auth/callback-tokens?access_token=${sessionValue?.access_token}&refresh_token=${sessionValue?.refresh_token}`
} else {
setAuthState(event);
}
});
}, []);

return (
<>
<p>Verifying login ...</p>
</>
);
}
212 changes: 5 additions & 207 deletions src/app/(dynamic-pages)/(login-pages)/login/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,29 +1,6 @@
'use client';
import { Email } from '@/components/Auth/Email';
import { EmailAndPassword } from '@/components/Auth/EmailAndPassword';
import { EmailConfirmationPendingCard } from '@/components/Auth/EmailConfirmationPendingCard';
import { RedirectingPleaseWaitCard } from '@/components/Auth/RedirectingPleaseWaitCard';
import { RenderProviders } from '@/components/Auth/RenderProviders';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
signInWithMagicLink,
signInWithPassword,
signInWithProvider,
} from '@/data/auth/auth';
import { getMaybeInitialOrganizationToRedirectTo } from '@/data/user/organizations';
import { useSAToastMutation } from '@/hooks/useSAToastMutation';
import type { AuthProvider } from '@/types';
import { PrefetchKind } from 'next/dist/client/components/router-reducer/router-reducer-types';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useDidMount } from 'rooks';
import DefaultLoginTabs from '@/components/Auth/DefaultLoginTabs';
import SSOLoginTabs from '@/components/Auth/SSOLoginTabs';

export function Login({
next,
Expand All @@ -32,188 +9,9 @@ export function Login({
next?: string;
nextActionType?: string;
}) {
const [emailSentSuccessMessage, setEmailSentSuccessMessage] = useState<string | null>(null);
const [redirectInProgress, setRedirectInProgress] = useState(false);

const router = useRouter();

useDidMount(() => {
console.log('prefetching dashboard')
router.prefetch('/org/123', {
kind: PrefetchKind.AUTO
})
})

const initialOrgRedirectMutation = useSAToastMutation(getMaybeInitialOrganizationToRedirectTo, {
loadingMessage: 'Loading your dashboard...',
errorMessage: 'Failed to load dashboard',
successMessage: 'Redirecting to your dashboard...',
onSuccess: (successPayload) => {
if (successPayload.data) {
router.push(`/org/${successPayload.data}`);
} else {
router.push('/dashboard');
}
},
onError: (errorPayload) => {
console.error(errorPayload);
},
dismissOnSuccess: true,
});


function redirectToDashboard() {
if (next) {
router.push(`/auth/callback?next=${next}`);
} else {
initialOrgRedirectMutation.mutate();
}
}
const magicLinkMutation = useSAToastMutation(
async (email: string) => {
return await signInWithMagicLink(email, next);
},
{
loadingMessage: 'Sending magic link...',
errorMessage(error) {
try {
if (error instanceof Error) {
return String(error.message);
}
return `Send magic link failed ${String(error)}`;
} catch (_err) {
console.warn(_err);
return 'Send magic link failed ';
}
},
successMessage: 'A magic link has been sent to your email!',
dismissOnSuccess: true,
onSuccess: () => {
setEmailSentSuccessMessage('A magic link has been sent to your email!');
},
},
);
const passwordMutation = useSAToastMutation(
async ({ email, password }: { email: string; password: string }) => {
return await signInWithPassword(email, password);
},
{
onSuccess: () => {
redirectToDashboard();
setRedirectInProgress(true);
},
loadingMessage: 'Logging in...',
errorMessage(error) {
try {
if (error instanceof Error) {
return String(error.message);
}
return `Sign in account failed ${String(error)}`;
} catch (_err) {
console.warn(_err);
return 'Sign in account failed';
}
},
successMessage: 'Logged in!',
dismissOnSuccess: true,
},
);
const providerMutation = useSAToastMutation(
async (provider: AuthProvider) => {
return signInWithProvider(provider, next);
},
{
loadingMessage: 'Requesting login...',
successMessage: 'Redirecting...',
errorMessage: 'Failed to login',
onSuccess: (payload) => {
window.location.href = payload.data.url;
},
dismissOnSuccess: true,
},
);
return (
<div
data-success={emailSentSuccessMessage}
className="container data-[success]:flex items-center data-[success]:justify-center text-left max-w-lg mx-auto overflow-auto data-[success]:h-full min-h-[470px]"
>
{emailSentSuccessMessage ? (
<EmailConfirmationPendingCard
type={'login'}
heading={"Confirmation Link Sent"}
message={emailSentSuccessMessage}
resetSuccessMessage={setEmailSentSuccessMessage}
/>
) : redirectInProgress ? (
<RedirectingPleaseWaitCard
message="Please wait while we redirect you to your dashboard."
heading="Redirecting to Dashboard"
/>
) : (
<div className="space-y-8 bg-background p-6 rounded-lg shadow dark:border">
<Tabs defaultValue="password" className="md:min-w-[400px]">
<TabsList className="grid w-full grid-cols-3">
<TabsTrigger value="password">Password</TabsTrigger>
<TabsTrigger value="magic-link">Magic Link</TabsTrigger>
<TabsTrigger value="social-login">Social Login</TabsTrigger>
</TabsList>
<TabsContent value="password">
<Card className="border-none shadow-none">
<CardHeader className="py-6 px-0">
<CardTitle>Login to Digger</CardTitle>
<CardDescription>
Login with the account you used to signup.
</CardDescription>
</CardHeader>
<CardContent className="space-y-2 p-0">
<EmailAndPassword
isLoading={passwordMutation.isLoading}
onSubmit={(data) => {
passwordMutation.mutate(data);
}}
view="sign-in"
/>
</CardContent>
</Card>
</TabsContent>

<TabsContent value="magic-link">
<Card className="border-none shadow-none">
<CardHeader className="py-6 px-0">
<CardTitle>Login to Digger</CardTitle>
<CardDescription>
Login with magic link we will send to your email.
</CardDescription>
</CardHeader>
<CardContent className="space-y-2 p-0">
<Email
onSubmit={(email) => magicLinkMutation.mutate(email)}
isLoading={magicLinkMutation.isLoading}
view="sign-in"
/>
</CardContent>
</Card>
</TabsContent>
<TabsContent value="social-login">
<Card className="border-none shadow-none">
<CardHeader className="py-6 px-0">
<CardTitle>Login to Digger</CardTitle>
<CardDescription>
Login with your social account.
</CardDescription>
</CardHeader>
<CardContent className="space-y-2 p-0">
<RenderProviders
providers={['github']}
isLoading={providerMutation.isLoading}
onProviderLoginRequested={providerMutation.mutate}
/>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
)}
</div>
<>
{process.env.NEXT_PUBLIC_SSO_DOMAIN ? <SSOLoginTabs></SSOLoginTabs> : <DefaultLoginTabs></DefaultLoginTabs>}
</>
);
}
6 changes: 6 additions & 0 deletions src/app/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import { NextResponse } from 'next/server';
export const dynamic = 'force-dynamic';

export async function GET() {
if (process.env.NEXT_PUBLIC_SSO_DOMAIN !== undefined) {
return NextResponse.redirect(
new URL('/auth/sso-verify', process.env.NEXT_PUBLIC_SITE_URL),
);
}

const supabase = createSupabaseUserRouteHandlerClient();

const {
Expand Down
Loading

0 comments on commit 0df1aa6

Please sign in to comment.