Files
fluxer/fluxer_app/src/router/routes/authRoutes.tsx
2026-01-02 19:27:51 +00:00

311 lines
9.0 KiB
TypeScript

/*
* Copyright (C) 2026 Fluxer Contributors
*
* This file is part of Fluxer.
*
* Fluxer is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Fluxer is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Fluxer. If not, see <https://www.gnu.org/licenses/>.
*/
import {i18n} from '@lingui/core';
import * as GiftActionCreators from '~/actions/GiftActionCreators';
import * as InviteActionCreators from '~/actions/InviteActionCreators';
import * as ThemeActionCreators from '~/actions/ThemeActionCreators';
import {AuthLayout} from '~/components/layout/AuthLayout';
import AuthorizeIPPage from '~/components/pages/AuthorizeIPPage';
import EmailRevertPage from '~/components/pages/EmailRevertPage';
import ForgotPasswordPage from '~/components/pages/ForgotPasswordPage';
import GiftLoginPage from '~/components/pages/GiftLoginPage';
import GiftRegisterPage from '~/components/pages/GiftRegisterPage';
import InviteLoginPage from '~/components/pages/InviteLoginPage';
import InviteRegisterPage from '~/components/pages/InviteRegisterPage';
import LoginPage from '~/components/pages/LoginPage';
import OAuthAuthorizePage from '~/components/pages/OAuthAuthorizePage';
import PendingVerificationPage from '~/components/pages/PendingVerificationPage';
import RegisterPage from '~/components/pages/RegisterPage';
import {ReportPage} from '~/components/pages/ReportPage';
import ResetPasswordPage from '~/components/pages/ResetPasswordPage';
import ThemeLoginPage from '~/components/pages/ThemeLoginPage';
import ThemeRegisterPage from '~/components/pages/ThemeRegisterPage';
import VerifyEmailPage from '~/components/pages/VerifyEmailPage';
import {createRoute, Redirect, type RouteContext} from '~/lib/router';
import SessionManager from '~/lib/SessionManager';
import {Routes} from '~/Routes';
import {rootRoute} from '~/router/routes/rootRoutes';
import AuthenticationStore from '~/stores/AuthenticationStore';
import RuntimeConfigStore from '~/stores/RuntimeConfigStore';
import * as RouterUtils from '~/utils/RouterUtils';
const resolveToPath = (to: Redirect['to']): string => {
if (typeof to === 'string') {
return to;
}
const url = new URL(to.to, window.location.origin);
if (to.search) {
const sp = new URLSearchParams();
for (const [k, v] of Object.entries(to.search)) {
if (v === undefined) continue;
if (v === null) {
sp.set(k, '');
} else {
sp.set(k, String(v));
}
}
url.search = sp.toString() ? `?${sp.toString()}` : '';
}
if (to.hash) {
url.hash = to.hash.startsWith('#') ? to.hash : `#${to.hash}`;
}
return url.pathname + url.search + url.hash;
};
type AuthRedirectHandler = (ctx: RouteContext) => Redirect | undefined;
const whenAuthenticated = (handler: AuthRedirectHandler) => {
return (ctx: RouteContext): Redirect | undefined => {
const execute = (): Redirect | undefined => handler(ctx);
if (SessionManager.isInitialized) {
return AuthenticationStore.isAuthenticated ? execute() : undefined;
}
void SessionManager.initialize().then(() => {
if (AuthenticationStore.isAuthenticated) {
const res = execute();
if (res instanceof Redirect) {
RouterUtils.replaceWith(resolveToPath(res.to));
}
}
});
return undefined;
};
};
const authLayoutRoute = createRoute({
getParentRoute: () => rootRoute,
id: 'authLayout',
layout: ({children}) => <AuthLayout>{children}</AuthLayout>,
});
const loginRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'login',
path: '/login',
onEnter: whenAuthenticated(() => {
const search = window.location.search;
const qp = new URLSearchParams(search);
const isDesktopHandoff = qp.get('desktop_handoff') === '1';
if (isDesktopHandoff) {
return undefined;
}
const redirectTo = qp.get('redirect_to');
return new Redirect(redirectTo || Routes.ME);
}),
component: () => <LoginPage />,
});
const registerRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'register',
path: '/register',
onEnter: whenAuthenticated(() => {
const search = window.location.search;
const qp = new URLSearchParams(search);
const redirectTo = qp.get('redirect_to');
return new Redirect(redirectTo || Routes.ME);
}),
component: () => <RegisterPage />,
});
const oauthAuthorizeRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'oauthAuthorize',
path: Routes.OAUTH_AUTHORIZE,
onEnter: () => {
const current = window.location.pathname + window.location.search;
if (!SessionManager.isInitialized) {
void SessionManager.initialize().then(() => {
if (!AuthenticationStore.isAuthenticated) {
RouterUtils.replaceWith(`${Routes.LOGIN}?redirect_to=${encodeURIComponent(current)}`);
}
});
return undefined;
}
if (!AuthenticationStore.isAuthenticated) {
return new Redirect(`${Routes.LOGIN}?redirect_to=${encodeURIComponent(current)}`);
}
return undefined;
},
component: () => <OAuthAuthorizePage />,
});
const inviteRegisterRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'inviteRegister',
path: '/invite/:code',
onEnter: whenAuthenticated((ctx) => {
const code = ctx.params.code;
if (code) {
InviteActionCreators.openAcceptModal(code);
}
return new Redirect(Routes.ME);
}),
component: () => <InviteRegisterPage />,
});
const inviteLoginRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'inviteLogin',
path: '/invite/:code/login',
onEnter: whenAuthenticated((ctx) => {
const code = ctx.params.code;
if (code) {
InviteActionCreators.openAcceptModal(code);
}
return new Redirect(Routes.ME);
}),
component: () => <InviteLoginPage />,
});
const giftRegisterRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'giftRegister',
path: '/gift/:code',
onEnter: whenAuthenticated((ctx) => {
const code = ctx.params.code;
if (code) {
GiftActionCreators.openAcceptModal(code);
}
return new Redirect(Routes.ME);
}),
component: () => <GiftRegisterPage />,
});
const giftLoginRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'giftLogin',
path: '/gift/:code/login',
onEnter: whenAuthenticated((ctx) => {
const code = ctx.params.code;
if (code) {
GiftActionCreators.openAcceptModal(code);
}
return new Redirect(Routes.ME);
}),
component: () => <GiftLoginPage />,
});
const forgotPasswordRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'forgotPassword',
path: Routes.FORGOT_PASSWORD,
onEnter: whenAuthenticated(() => new Redirect(Routes.ME)),
component: () => <ForgotPasswordPage />,
});
const resetPasswordRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'resetPassword',
path: Routes.RESET_PASSWORD,
component: () => <ResetPasswordPage />,
});
const emailRevertRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'emailRevert',
path: Routes.EMAIL_REVERT,
component: () => <EmailRevertPage />,
});
const verifyEmailRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'verifyEmail',
path: Routes.VERIFY_EMAIL,
component: () => <VerifyEmailPage />,
});
const authorizeIPRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'authorizeIP',
path: Routes.AUTHORIZE_IP,
component: () => <AuthorizeIPPage />,
});
const pendingVerificationRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'pendingVerification',
path: Routes.PENDING_VERIFICATION,
component: () => <PendingVerificationPage />,
});
const reportRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'report',
path: Routes.REPORT,
component: () => <ReportPage />,
});
const themeRegisterRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'themeRegister',
path: Routes.THEME_REGISTER,
onEnter: whenAuthenticated((ctx) => {
const themeId = ctx.params.themeId;
if (themeId) {
ThemeActionCreators.openAcceptModal(themeId, i18n);
}
return new Redirect(Routes.ME);
}),
component: () => <ThemeRegisterPage />,
});
const themeLoginRoute = createRoute({
getParentRoute: () => authLayoutRoute,
id: 'themeLogin',
path: Routes.THEME_LOGIN,
onEnter: whenAuthenticated((ctx) => {
const themeId = ctx.params.themeId;
if (themeId) {
ThemeActionCreators.openAcceptModal(themeId, i18n);
}
return new Redirect(Routes.ME);
}),
component: () => <ThemeLoginPage />,
});
export const authRouteTree = authLayoutRoute.addChildren([
loginRoute,
registerRoute,
oauthAuthorizeRoute,
inviteRegisterRoute,
inviteLoginRoute,
themeRegisterRoute,
themeLoginRoute,
forgotPasswordRoute,
resetPasswordRoute,
emailRevertRoute,
verifyEmailRoute,
authorizeIPRoute,
pendingVerificationRoute,
reportRoute,
...(RuntimeConfigStore.isSelfHosted() ? [] : [giftRegisterRoute, giftLoginRoute]),
]);