-
Notifications
You must be signed in to change notification settings - Fork 0
Oauth #81
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
kapitulin24
wants to merge
5
commits into
dev
Choose a base branch
from
refactor/oauth
base: dev
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Oauth #81
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
05a1e0d
refactor: update OAuth API endpoint URLs
AlexandrNel 7aec8a3
refactor: restructure OAuth authentication flow
AlexandrNel 2c48561
refactor: replace OAuthButton with dynamic loading for improved perfo…
kapitulin24 c4783f1
refactor: clean up OAuthLoginButtonsContent and improve redirect logic
kapitulin24 fe6c727
Merge branch 'dev' into refactor/oauth
kapitulin24 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import { type TAuth } from 'entities/auth'; | ||
| import { StartOauthParams } from 'features/auth/oauth-login'; | ||
| import { NextRequest, NextResponse } from 'next/server'; | ||
| import { env, routes } from 'shared/config'; | ||
|
|
||
| type OAuthParams = | ||
| | { | ||
| success: 'false' | 'true'; | ||
| token?: string; | ||
| provider: TAuth.OAuthProvider; | ||
| } | ||
| | StartOauthParams; | ||
|
|
||
| const ERROR_MESSAGE = 'Не удалось выполнить авторизацию'; | ||
|
|
||
| export async function GET(request: NextRequest) { | ||
| const params: Partial<OAuthParams> = Object.fromEntries(request.nextUrl.searchParams); | ||
|
|
||
| //start oauth | ||
| if ( | ||
| 'provider' in params && | ||
| 'startOAuth' in params && | ||
| params.startOAuth === 'true' && | ||
| params.provider | ||
| ) { | ||
| const { provider } = params; | ||
| const redirectUrl = `${env.NEXT_PUBLIC_API_BASE_URL}/oauth/${provider}`; | ||
|
|
||
| return NextResponse.redirect(redirectUrl); | ||
| } | ||
|
|
||
| //exchange token | ||
| if ('token' in params && params.token) { | ||
| try { | ||
| const { token, success, provider } = params; | ||
|
|
||
| if (success === 'false') { | ||
| throw new Error(ERROR_MESSAGE); | ||
| } | ||
|
|
||
| if (!provider) { | ||
| throw new Error('OAuth provider не найден в query параметрах'); | ||
| } | ||
|
|
||
| const response = await fetch(`${env.NEXT_PUBLIC_API_BASE_URL}/oauth/exchange`, { | ||
| method: 'POST', | ||
| headers: { | ||
| 'Content-Type': 'application/json', | ||
| }, | ||
| body: JSON.stringify({ token, provider } satisfies TAuth.ExchangeTokenBody), | ||
| }); | ||
|
|
||
| const data = (await response.json()) as TAuth.ExchangeTokenResponse; | ||
|
|
||
| if (!response.ok || !data.success) { | ||
| throw new Error( | ||
| data.message || `OAuth exchange завершился с ошибкой (status ${response.status})` | ||
| ); | ||
| } | ||
|
|
||
| const successUrl = new URL(routes.user.profile(), request.url); | ||
|
|
||
| successUrl.searchParams.set('success', 'true'); | ||
| successUrl.searchParams.set('message', 'Операция выполнена успешно'); | ||
|
|
||
| const res = NextResponse.redirect(successUrl); | ||
| const cookies = response.headers.getSetCookie() ?? []; | ||
|
|
||
| cookies.forEach((cookie) => { | ||
| res.headers.append('Set-Cookie', cookie); | ||
| }); | ||
|
|
||
| return res; | ||
| } catch (error) { | ||
| console.error(error instanceof Error ? error.message : ERROR_MESSAGE); | ||
| } | ||
| } | ||
|
|
||
| const errorUrl = new URL(routes.auth.signin(), request.url); | ||
|
|
||
| errorUrl.searchParams.set('success', 'false'); | ||
| errorUrl.searchParams.set('message', ERROR_MESSAGE); | ||
|
|
||
| return NextResponse.redirect(errorUrl); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,26 +1,26 @@ | ||
| import { type TAuth } from 'entities/auth'; | ||
| import YandexIcon from 'public/yandex-logo.svg'; | ||
| import VkontakteIcon from 'public/vkontakte-logo.svg'; | ||
| import GoogleIcon from 'public/google-logo.svg'; | ||
| import GithubIcon from 'public/github-logo.svg'; | ||
| import GithubIcon from 'github-logo.svg'; | ||
| import GoogleIcon from 'google-logo.svg'; | ||
| import VkontakteIcon from 'vkontakte-logo.svg'; | ||
| import YandexIcon from 'yandex-logo.svg'; | ||
| import { OAuthProvider } from '../model/types'; | ||
|
|
||
| export type OAuthProviderMeta = { | ||
| iconSrc: string; | ||
| buttonClassName?: string; | ||
| className?: string; | ||
| }; | ||
|
|
||
| export const OAUTH_PROVIDERS: Record<TAuth.OAuthProvider, OAuthProviderMeta> = { | ||
| export const OAUTH_PROVIDERS: Record<OAuthProvider, OAuthProviderMeta> = { | ||
| yandex: { | ||
| iconSrc: YandexIcon, | ||
| buttonClassName: 'text-[#fc3f1d] hover:text-[#fc3f1d]', | ||
| className: 'text-[#fc3f1d] hover:text-[#fc3f1d]', | ||
| }, | ||
| vkontakte: { | ||
| iconSrc: VkontakteIcon, | ||
| buttonClassName: 'bg-[#07f] hover:bg-[#07f]', | ||
| className: 'bg-[#07f] hover:bg-[#07f]', | ||
| }, | ||
| google: { iconSrc: GoogleIcon }, | ||
| github: { | ||
| iconSrc: GithubIcon, | ||
| buttonClassName: 'bg-[#24292f] hover:bg-[#24292f] text-white hover:text-white ', | ||
| className: 'bg-[#24292f] hover:bg-[#24292f] text-white hover:text-white ', | ||
| }, | ||
| } as const; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,16 @@ | ||
| 'use client'; | ||
| import dynamic from 'next/dynamic'; | ||
| import { Skeleton } from 'shared/ui'; | ||
| import { cn } from 'shared/lib/utils'; | ||
| import { useQuery } from '@tanstack/react-query'; | ||
| import { AuthQueries } from 'entities/auth'; | ||
| import { routes } from 'shared/config'; | ||
| import { OAuthButton } from './OAuthButton'; | ||
| import { type TAuth } from 'entities/auth'; | ||
| import { type StartOauthParams } from '../model/types'; | ||
| import { type Route } from 'next'; | ||
|
|
||
| export const getRoute = (provider: TAuth.OAuthProvider) => { | ||
| const params = new URLSearchParams({ | ||
| provider, | ||
| startOAuth: 'true', | ||
| } satisfies Record<keyof StartOauthParams, string>); | ||
|
|
||
| return `${routes.auth.oauth()}?${params.toString()}`; | ||
| }; | ||
|
|
||
| export function OAuthLoginButtons({ className }: { className?: string }) { | ||
| const { data, isLoading } = useQuery(AuthQueries.getOAuthProviders()); | ||
|
|
||
| return ( | ||
| <div className={cn('flex items-center justify-center gap-2', className)}> | ||
| {isLoading && Array.from({ length: 3 }, (_v, i) => <Skeleton className="size-8" key={i} />)} | ||
|
|
||
| {data?.map((item) => { | ||
| return <OAuthButton key={item.value} data={item} href={getRoute(item.value) as Route} />; | ||
| })} | ||
| </div> | ||
| ); | ||
| } | ||
| export const OAuthLoginButtons = dynamic( | ||
| () => import('./OAuthLoginButtonsContent').then((module) => module.OAuthLoginButtonsContent), | ||
| { | ||
| ssr: false, | ||
| loading: () => ( | ||
| <div className="flex items-center justify-center gap-2"> | ||
| {Array.from({ length: 3 }, (_v, i) => ( | ||
| <Skeleton className="size-8" key={i} /> | ||
| ))} | ||
| </div> | ||
| ), | ||
| } | ||
| ); |
47 changes: 47 additions & 0 deletions
47
src/features/auth/oauth-login/ui/OAuthLoginButtonsContent.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| 'use client'; | ||
|
|
||
| import { useSuspenseQuery } from '@tanstack/react-query'; | ||
| import { AuthQueries, OAUTH_PROVIDERS, type TAuth } from 'entities/auth'; | ||
| import Image from 'next/image'; | ||
| import { useCallback } from 'react'; | ||
| import { routes } from 'shared/config'; | ||
| import { cn } from 'shared/lib/utils'; | ||
| import { Button } from 'shared/ui'; | ||
|
|
||
| export interface OAuthLoginButtonsContentProps { | ||
| className?: string; | ||
| } | ||
|
|
||
| export function OAuthLoginButtonsContent({ className }: OAuthLoginButtonsContentProps) { | ||
| const { data } = useSuspenseQuery(AuthQueries.getOAuthProviders()); | ||
|
|
||
| const startOAuth = useCallback((provider: TAuth.OAuthProvider) => { | ||
| const route = new URL(routes.auth.oauth(), window.location.origin); | ||
|
|
||
| route.searchParams.set('provider', provider); | ||
| route.searchParams.set('startOAuth', 'true'); | ||
|
|
||
| window.location.assign(route.href); | ||
| }, []); | ||
|
|
||
| return ( | ||
| <div className={cn('flex items-center justify-center gap-2', className)}> | ||
| {data?.map(({ label, value }) => { | ||
| const data = OAUTH_PROVIDERS[value]; | ||
|
|
||
| return ( | ||
| <Button | ||
| type="button" | ||
| className={data.className} | ||
| key={value} | ||
| variant={'outline'} | ||
| size="icon" | ||
| onClick={() => startOAuth(value)} | ||
| > | ||
| <Image src={data.iconSrc} alt={label} width={24} height={24} className="size-6" /> | ||
| </Button> | ||
| ); | ||
| })} | ||
| </div> | ||
| ); | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,16 @@ | ||
| import { cn } from 'shared/lib/utils'; | ||
| import dynamic from 'next/dynamic'; | ||
| import { Skeleton } from 'shared/ui'; | ||
|
|
||
| interface OAuthSeparatorProps { | ||
| className?: string; | ||
| label?: string; | ||
| } | ||
|
|
||
| export function OAuthSeparator({ className, label = 'или' }: OAuthSeparatorProps) { | ||
| return ( | ||
| <div className={cn('text-muted-foreground my-3 flex items-center', className)}> | ||
| <span className="bg-border h-px w-full" /> | ||
| <span className="block px-2">{label}</span> | ||
| <span className="bg-border h-px w-full" /> | ||
| </div> | ||
| ); | ||
| } | ||
| export const OAuthSeparator = dynamic( | ||
| () => import('./OAuthSeparatorContent').then((module) => module.OAuthSeparatorContent), | ||
| { | ||
| ssr: false, | ||
| loading: () => ( | ||
| <div className="my-3 flex items-center px-1"> | ||
| <Skeleton className="h-px w-full" /> | ||
| <Skeleton className="mx-2 h-4 w-16" /> | ||
| <Skeleton className="h-px w-full" /> | ||
| </div> | ||
| ), | ||
| } | ||
| ); |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.