mirror of
https://github.com/toeverything/AFFiNE.git
synced 2026-08-01 17:39:55 +08:00
refactor(server): auth (#5895)
Remove `next-auth` and implement our own Authorization/Authentication system from scratch.
## Server
- [x] tokens
- [x] function
- [x] encryption
- [x] AuthController
- [x] /api/auth/sign-in
- [x] /api/auth/sign-out
- [x] /api/auth/session
- [x] /api/auth/session (WE SUPPORT MULTI-ACCOUNT!)
- [x] OAuthPlugin
- [x] OAuthController
- [x] /oauth/login
- [x] /oauth/callback
- [x] Providers
- [x] Google
- [x] GitHub
## Client
- [x] useSession
- [x] cloudSignIn
- [x] cloudSignOut
## NOTE:
Tests will be adding in the future
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
import { NotFoundPage } from '@affine/component/not-found-page';
|
||||
import { useSession } from '@affine/core/hooks/affine/use-current-user';
|
||||
import { useAsyncCallback } from '@affine/core/hooks/affine-async-hooks';
|
||||
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
|
||||
import { useSession } from 'next-auth/react';
|
||||
import type { ReactElement } from 'react';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
@@ -10,7 +9,7 @@ import { RouteLogic, useNavigateHelper } from '../hooks/use-navigate-helper';
|
||||
import { signOutCloud } from '../utils/cloud-utils';
|
||||
|
||||
export const PageNotFound = (): ReactElement => {
|
||||
const { data: session } = useSession();
|
||||
const { user } = useSession();
|
||||
const { jumpToIndex } = useNavigateHelper();
|
||||
const [open, setOpen] = useState(false);
|
||||
|
||||
@@ -25,22 +24,12 @@ export const PageNotFound = (): ReactElement => {
|
||||
|
||||
const onConfirmSignOut = useAsyncCallback(async () => {
|
||||
setOpen(false);
|
||||
await signOutCloud({
|
||||
callbackUrl: '/signIn',
|
||||
});
|
||||
await signOutCloud('/signIn');
|
||||
}, [setOpen]);
|
||||
return (
|
||||
<>
|
||||
<NotFoundPage
|
||||
user={
|
||||
session?.user
|
||||
? {
|
||||
name: session.user.name || '',
|
||||
email: session.user.email || '',
|
||||
avatar: session.user.image || '',
|
||||
}
|
||||
: null
|
||||
}
|
||||
user={user}
|
||||
onBack={handleBackButtonClick}
|
||||
onSignOut={handleOpenSignOutModal}
|
||||
/>
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
changeEmailMutation,
|
||||
changePasswordMutation,
|
||||
sendVerifyChangeEmailMutation,
|
||||
verifyEmailMutation,
|
||||
} from '@affine/graphql';
|
||||
import { fetcher } from '@affine/graphql';
|
||||
import { useAFFiNEI18N } from '@affine/i18n/hooks';
|
||||
@@ -42,6 +43,7 @@ const authTypeSchema = z.enum([
|
||||
'changeEmail',
|
||||
'confirm-change-email',
|
||||
'subscription-redirect',
|
||||
'verify-email',
|
||||
]);
|
||||
|
||||
export const AuthPage = (): ReactElement | null => {
|
||||
@@ -73,12 +75,10 @@ export const AuthPage = (): ReactElement | null => {
|
||||
// FIXME: There is not notification
|
||||
if (res?.sendVerifyChangeEmail) {
|
||||
pushNotification({
|
||||
title: t['com.affine.auth.sent.change.email.hint'](),
|
||||
title: t['com.affine.auth.sent.verify.email.hint'](),
|
||||
type: 'success',
|
||||
});
|
||||
}
|
||||
|
||||
if (!res?.sendVerifyChangeEmail) {
|
||||
} else {
|
||||
pushNotification({
|
||||
title: t['com.affine.auth.sent.change.email.fail'](),
|
||||
type: 'error',
|
||||
@@ -156,6 +156,9 @@ export const AuthPage = (): ReactElement | null => {
|
||||
case 'subscription-redirect': {
|
||||
return <SubscriptionRedirect />;
|
||||
}
|
||||
case 'verify-email': {
|
||||
return <ConfirmChangeEmail onOpenAffine={onOpenAffine} />;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -171,20 +174,37 @@ export const loader: LoaderFunction = async args => {
|
||||
if (args.params.authType === 'confirm-change-email') {
|
||||
const url = new URL(args.request.url);
|
||||
const searchParams = url.searchParams;
|
||||
const token = searchParams.get('token');
|
||||
const token = searchParams.get('token') ?? '';
|
||||
const email = decodeURIComponent(searchParams.get('email') ?? '');
|
||||
const res = await fetcher({
|
||||
query: changeEmailMutation,
|
||||
variables: {
|
||||
token: token || '',
|
||||
token: token,
|
||||
email: email,
|
||||
},
|
||||
}).catch(console.error);
|
||||
// TODO: Add error handling
|
||||
if (!res?.changeEmail) {
|
||||
return redirect('/expired');
|
||||
}
|
||||
} else if (args.params.authType === 'verify-email') {
|
||||
const url = new URL(args.request.url);
|
||||
const searchParams = url.searchParams;
|
||||
const token = searchParams.get('token') ?? '';
|
||||
const res = await fetcher({
|
||||
query: verifyEmailMutation,
|
||||
variables: {
|
||||
token: token,
|
||||
},
|
||||
}).catch(console.error);
|
||||
|
||||
if (!res?.verifyEmail) {
|
||||
return redirect('/expired');
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const Component = () => {
|
||||
const loginStatus = useCurrentLoginStatus();
|
||||
const { jumpToExpired } = useNavigateHelper();
|
||||
|
||||
@@ -1,34 +1,43 @@
|
||||
import { getSession } from 'next-auth/react';
|
||||
import { OAuthProviderType } from '@affine/graphql';
|
||||
import { type LoaderFunction } from 'react-router-dom';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { getSession } from '../hooks/affine/use-current-user';
|
||||
import { signInCloud, signOutCloud } from '../utils/cloud-utils';
|
||||
|
||||
const supportedProvider = z.enum(['google']);
|
||||
const supportedProvider = z.enum([
|
||||
'google',
|
||||
...Object.values(OAuthProviderType),
|
||||
]);
|
||||
|
||||
export const loader: LoaderFunction = async ({ request }) => {
|
||||
const url = new URL(request.url);
|
||||
const searchParams = url.searchParams;
|
||||
const provider = searchParams.get('provider');
|
||||
const callback_url = searchParams.get('callback_url');
|
||||
if (!callback_url) {
|
||||
const redirectUri =
|
||||
searchParams.get('redirect_uri') ??
|
||||
/* backward compatibility */ searchParams.get('callback_url');
|
||||
|
||||
if (!redirectUri) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const session = await getSession();
|
||||
|
||||
if (session) {
|
||||
if (session.user) {
|
||||
// already signed in, need to sign out first
|
||||
await signOutCloud({
|
||||
callbackUrl: request.url, // retry
|
||||
});
|
||||
await signOutCloud(request.url);
|
||||
}
|
||||
|
||||
const maybeProvider = supportedProvider.safeParse(provider);
|
||||
if (maybeProvider.success) {
|
||||
const provider = maybeProvider.data;
|
||||
await signInCloud(provider, {
|
||||
callbackUrl: callback_url,
|
||||
let provider = maybeProvider.data;
|
||||
// BACKWARD COMPATIBILITY
|
||||
if (provider === 'google') {
|
||||
provider = OAuthProviderType.Google;
|
||||
}
|
||||
await signInCloud(provider, undefined, {
|
||||
redirectUri,
|
||||
});
|
||||
}
|
||||
return null;
|
||||
|
||||
Reference in New Issue
Block a user