Login: add tenant-list route
This commit is contained in:
parent
ffe98ad175
commit
d7914ad024
@ -24,7 +24,7 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { IClientProps } from "@docspace/shared/utils/oauth/interfaces";
|
||||
import { IClientProps } from "@docspace/shared/utils/oauth/types";
|
||||
|
||||
import Consent from "@/components/Consent";
|
||||
import { getOAuthClient, getScopeList, getUser } from "@/utils/actions";
|
||||
@ -36,7 +36,7 @@ async function Page({
|
||||
}) {
|
||||
const clientId = searchParams.clientId ?? searchParams.client_id;
|
||||
const [client, scopes, user] = await Promise.all([
|
||||
getOAuthClient(clientId, true),
|
||||
getOAuthClient(clientId),
|
||||
getScopeList(),
|
||||
getUser(),
|
||||
]);
|
||||
|
@ -83,7 +83,7 @@ export default async function Layout({
|
||||
<GreetingContainer
|
||||
greetingSettings={objectSettings?.greetingSettings}
|
||||
/>
|
||||
<FormWrapper id="login-form">{children}</FormWrapper>
|
||||
{children}
|
||||
</ColorTheme>
|
||||
</LoginContent>
|
||||
</Scrollbar>
|
||||
|
@ -24,58 +24,56 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { INoAuthClientProps } from "@docspace/shared/utils/oauth/interfaces";
|
||||
import { INoAuthClientProps } from "@docspace/shared/utils/oauth/types";
|
||||
|
||||
import { getConfig, getOAuthClient, getSettings } from "@/utils/actions";
|
||||
import { getOAuthClient, getSettings } from "@/utils/actions";
|
||||
import Login from "@/components/Login";
|
||||
import LoginForm from "@/components/LoginForm";
|
||||
import ThirdParty from "@/components/ThirdParty";
|
||||
import RecoverAccess from "@/components/RecoverAccess";
|
||||
import Register from "@/components/Register";
|
||||
import { FormWrapper } from "@docspace/shared/components/form-wrapper";
|
||||
|
||||
async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { [key: string]: string };
|
||||
}) {
|
||||
const clientId = searchParams.clientId;
|
||||
const clientId = searchParams.client_id;
|
||||
|
||||
const [settings, client, config] = await Promise.all([
|
||||
const [settings, client] = await Promise.all([
|
||||
getSettings(),
|
||||
clientId ? getOAuthClient(clientId, false) : undefined,
|
||||
clientId ? getConfig() : undefined,
|
||||
clientId ? getOAuthClient(clientId) : undefined,
|
||||
]);
|
||||
|
||||
const isPublicOAuth = clientId && config.oauth2.publicClient;
|
||||
|
||||
console.log(isPublicOAuth);
|
||||
|
||||
return (
|
||||
<Login>
|
||||
{settings && typeof settings !== "string" && (
|
||||
<>
|
||||
<LoginForm
|
||||
hashSettings={settings?.passwordHash}
|
||||
cookieSettingsEnabled={settings?.cookieSettingsEnabled}
|
||||
clientId={clientId}
|
||||
client={client as INoAuthClientProps}
|
||||
reCaptchaPublicKey={settings?.recaptchaPublicKey}
|
||||
reCaptchaType={settings?.recaptchaType}
|
||||
/>
|
||||
{!clientId && <ThirdParty />}
|
||||
{settings.enableAdmMess && <RecoverAccess />}
|
||||
{settings.enabledJoin && !clientId && (
|
||||
<Register
|
||||
id="login_register"
|
||||
enabledJoin
|
||||
trustedDomains={settings.trustedDomains}
|
||||
trustedDomainsType={settings.trustedDomainsType}
|
||||
isAuthenticated={false}
|
||||
<FormWrapper id="login-form">
|
||||
<Login>
|
||||
{settings && typeof settings !== "string" && (
|
||||
<>
|
||||
<LoginForm
|
||||
hashSettings={settings?.passwordHash}
|
||||
cookieSettingsEnabled={settings?.cookieSettingsEnabled}
|
||||
clientId={clientId}
|
||||
client={client}
|
||||
reCaptchaPublicKey={settings?.recaptchaPublicKey}
|
||||
reCaptchaType={settings?.recaptchaType}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Login>
|
||||
{!clientId && <ThirdParty />}
|
||||
{settings.enableAdmMess && <RecoverAccess />}
|
||||
{settings.enabledJoin && !clientId && (
|
||||
<Register
|
||||
id="login_register"
|
||||
enabledJoin
|
||||
trustedDomains={settings.trustedDomains}
|
||||
trustedDomainsType={settings.trustedDomainsType}
|
||||
isAuthenticated={false}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Login>
|
||||
</FormWrapper>
|
||||
);
|
||||
}
|
||||
|
||||
|
23
packages/login/src/app/(root)/tenant-list/page.tsx
Normal file
23
packages/login/src/app/(root)/tenant-list/page.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
import TenantList from "@/components/TenantList";
|
||||
import { getSettings } from "@/utils/actions";
|
||||
|
||||
export default async function Page({
|
||||
searchParams,
|
||||
}: {
|
||||
searchParams: { [key: string]: string };
|
||||
}) {
|
||||
const settings = await getSettings();
|
||||
|
||||
const { portals } = JSON.parse(searchParams.portals);
|
||||
const clientId = searchParams.clientId;
|
||||
|
||||
if (typeof settings !== "object") return;
|
||||
|
||||
return (
|
||||
<TenantList
|
||||
portals={portals}
|
||||
clientId={clientId}
|
||||
baseDomain={settings.baseDomain}
|
||||
/>
|
||||
);
|
||||
}
|
@ -41,12 +41,13 @@ import {
|
||||
AvatarSize,
|
||||
} from "@docspace/shared/components/avatar";
|
||||
import { deleteCookie } from "@docspace/shared/utils/cookie";
|
||||
import { IClientProps, IScope } from "@docspace/shared/utils/oauth/interfaces";
|
||||
import { IClientProps, IScope } from "@docspace/shared/utils/oauth/types";
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import api from "@docspace/shared/api";
|
||||
|
||||
import OAuthClientInfo from "./ConsentInfo";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { FormWrapper } from "@docspace/shared/components/form-wrapper";
|
||||
|
||||
const StyledButtonContainer = styled.div`
|
||||
margin-top: 32px;
|
||||
@ -158,11 +159,11 @@ const Consent = ({ client, scopes, user }: IConsentProps) => {
|
||||
const onChangeUserClick = async () => {
|
||||
await api.user.logout();
|
||||
|
||||
router.push(`/?clientId=${client.clientId}`);
|
||||
router.push(`/?client_id=${client.clientId}&type=oauth2`);
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormWrapper>
|
||||
<OAuthClientInfo
|
||||
name={client.name}
|
||||
logo={client.logo}
|
||||
@ -198,7 +199,7 @@ const Consent = ({ client, scopes, user }: IConsentProps) => {
|
||||
<StyledDescriptionContainer>
|
||||
<Text fontWeight={400} fontSize={"13px"} lineHeight={"20px"}>
|
||||
<Trans t={t} i18nKey={"ConsentDescription"} ns="Consent">
|
||||
Data shared with {{ displayName: self.displayName }} will be
|
||||
Data shared with {{ displayName: user.displayName }} will be
|
||||
governed by {{ nameApp: client.name }}
|
||||
<Link
|
||||
className={"login-link"}
|
||||
@ -250,7 +251,7 @@ const Consent = ({ client, scopes, user }: IConsentProps) => {
|
||||
</div>
|
||||
</div>
|
||||
</StyledUserContainer>
|
||||
</>
|
||||
</FormWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -29,7 +29,7 @@
|
||||
|
||||
import React, { useLayoutEffect, useState } from "react";
|
||||
import { Trans, useTranslation } from "react-i18next";
|
||||
import { useSearchParams } from "next/navigation";
|
||||
import { usePathname, useSearchParams } from "next/navigation";
|
||||
import { useTheme } from "styled-components";
|
||||
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
@ -48,6 +48,7 @@ const GreetingContainer = ({ greetingSettings }: GreetingContainersProps) => {
|
||||
const logoUrl = getLogoUrl(WhiteLabelLogoType.LoginPage, !theme.isBase);
|
||||
|
||||
const searchParams = useSearchParams();
|
||||
const pathname = usePathname();
|
||||
|
||||
const [invitationLinkData, setInvitationLinkData] = useState({
|
||||
email: "",
|
||||
@ -84,7 +85,9 @@ const GreetingContainer = ({ greetingSettings }: GreetingContainersProps) => {
|
||||
textAlign="center"
|
||||
className="greeting-title"
|
||||
>
|
||||
{greetingSettings}
|
||||
{pathname === "/tenant-list"
|
||||
? "Choose your portal"
|
||||
: greetingSettings}
|
||||
</Text>
|
||||
)}
|
||||
|
||||
|
@ -51,9 +51,10 @@ import { setWithCredentialsStatus } from "@docspace/shared/api/client";
|
||||
import { TValidate } from "@docspace/shared/components/email-input/EmailInput.types";
|
||||
import api from "@docspace/shared/api";
|
||||
import { RecaptchaType } from "@docspace/shared/enums";
|
||||
import { getAvailablePortals } from "@docspace/shared/api/management";
|
||||
|
||||
import { LoginFormProps } from "@/types";
|
||||
import { getEmailFromInvitation } from "@/utils";
|
||||
import { generateOAuth2ReferenceURl, getEmailFromInvitation } from "@/utils";
|
||||
|
||||
import EmailContainer from "./sub-components/EmailContainer";
|
||||
import PasswordContainer from "./sub-components/PasswordContainer";
|
||||
@ -63,6 +64,7 @@ import LDAPContainer from "./sub-components/LDAPContainer";
|
||||
import { StyledCaptcha } from "./LoginForm.styled";
|
||||
import { LoginDispatchContext, LoginValueContext } from "../Login";
|
||||
import OAuthClientInfo from "../ConsentInfo";
|
||||
// import { gitAvailablePortals } from "@/utils/actions";
|
||||
|
||||
const LoginForm = ({
|
||||
hashSettings,
|
||||
@ -204,7 +206,7 @@ const LoginForm = ({
|
||||
if (!passwordValid) setPasswordValid(true);
|
||||
};
|
||||
|
||||
const onSubmit = useCallback(() => {
|
||||
const onSubmit = useCallback(async () => {
|
||||
//errorText && setErrorText("");
|
||||
let captchaToken: string | undefined | null = "";
|
||||
|
||||
@ -254,6 +256,32 @@ const LoginForm = ({
|
||||
isDesktop && checkPwd();
|
||||
const session = !isChecked;
|
||||
|
||||
if (client?.isPublic && hash) {
|
||||
const portals = await getAvailablePortals({
|
||||
Email: user,
|
||||
PasswordHash: hash,
|
||||
});
|
||||
|
||||
// if (portals.length === 1) {
|
||||
// const referenceUrl = generateOAuth2ReferenceURl(client.clientId);
|
||||
// window.open(
|
||||
// `${portals[0].portalLink}&referenceUrl=${referenceUrl}`,
|
||||
// "_self",
|
||||
// );
|
||||
// }
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
|
||||
const portalsString = JSON.stringify({ portals });
|
||||
|
||||
searchParams.set("portals", portalsString);
|
||||
searchParams.set("clientId", client.clientId);
|
||||
|
||||
router.push(`/tenant-list?${searchParams.toString()}`);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
login(user, hash, pwd, session, captchaToken, currentCulture, reCaptchaType)
|
||||
.then(async (res: string | object) => {
|
||||
if (clientId) {
|
||||
@ -307,17 +335,18 @@ const LoginForm = ({
|
||||
password,
|
||||
identifierValid,
|
||||
setIsLoading,
|
||||
isLdapLoginChecked,
|
||||
hashSettings,
|
||||
isDesktop,
|
||||
isChecked,
|
||||
isLdapLoginChecked,
|
||||
|
||||
client?.isPublic,
|
||||
client?.clientId,
|
||||
currentCulture,
|
||||
reCaptchaType,
|
||||
isCaptchaSuccessful,
|
||||
router,
|
||||
clientId,
|
||||
referenceUrl,
|
||||
currentCulture,
|
||||
router,
|
||||
reCaptchaType,
|
||||
]);
|
||||
|
||||
const onBlurEmail = () => {
|
||||
|
@ -0,0 +1,78 @@
|
||||
import { mobile } from "@docspace/shared/utils";
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledTenantList = styled.div`
|
||||
margin-top: -16px;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
.more-accounts {
|
||||
color: ${(props) => props.theme.text.disableColor};
|
||||
text-align: center;
|
||||
|
||||
margin-bottom: 32px;
|
||||
}
|
||||
|
||||
.items-list {
|
||||
width: 100%;
|
||||
max-width: 480px;
|
||||
|
||||
border: 1px solid ${(props) => props.theme.oauth.infoDialog.separatorColor};
|
||||
border-radius: 6px;
|
||||
|
||||
div:last-child {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
@media ${mobile} {
|
||||
maxwidth: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.item {
|
||||
height: 59px;
|
||||
|
||||
box-sizing: border-box;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
padding: 0 16px;
|
||||
|
||||
border-bottom: 1px solid
|
||||
${(props) => props.theme.oauth.infoDialog.separatorColor};
|
||||
|
||||
:hover {
|
||||
cursor: pointer;
|
||||
|
||||
background-color: ${(props) =>
|
||||
props.theme.dropDownItem.hoverBackgroundColor};
|
||||
}
|
||||
|
||||
.info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
max-width: calc(100% - 64px);
|
||||
}
|
||||
|
||||
.favicon {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
.icon-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.back-button {
|
||||
margin: 32px auto 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export default StyledTenantList;
|
13
packages/login/src/components/TenantList/TenantList.types.ts
Normal file
13
packages/login/src/components/TenantList/TenantList.types.ts
Normal file
@ -0,0 +1,13 @@
|
||||
type TPortal = { portalLink: string; portalName: string };
|
||||
|
||||
export type TenantListProps = {
|
||||
baseDomain: string;
|
||||
clientId: string;
|
||||
portals: TPortal[];
|
||||
};
|
||||
|
||||
export type ItemProps = {
|
||||
portal: TPortal;
|
||||
baseDomain: string;
|
||||
clientId: string;
|
||||
};
|
43
packages/login/src/components/TenantList/index.tsx
Normal file
43
packages/login/src/components/TenantList/index.tsx
Normal file
@ -0,0 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
|
||||
import Item from "./sub-components/Item";
|
||||
|
||||
import StyledTenantList from "./TenantList.styled";
|
||||
import { TenantListProps } from "./TenantList.types";
|
||||
import { Button } from "@docspace/shared/components/button";
|
||||
import { useRouter } from "next/navigation";
|
||||
|
||||
const TenantList = ({ portals, clientId, baseDomain }: TenantListProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const goToLogin = () => {
|
||||
router.push(`/?type=oauth2&client_id=${clientId}`);
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledTenantList>
|
||||
<Text className="more-accounts">
|
||||
You have more than one accounts. Please choose one of them
|
||||
</Text>
|
||||
<div className="items-list">
|
||||
{portals.map((item) => (
|
||||
<Item
|
||||
portal={item}
|
||||
key={item.portalName}
|
||||
clientId={clientId}
|
||||
baseDomain={baseDomain}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<Button
|
||||
onClick={goToLogin}
|
||||
label="Back to sign in"
|
||||
className="back-button"
|
||||
/>
|
||||
</StyledTenantList>
|
||||
);
|
||||
};
|
||||
|
||||
export default TenantList;
|
@ -0,0 +1,44 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
|
||||
import { Text } from "@docspace/shared/components/text";
|
||||
|
||||
import ArrowRightSvrUrl from "PUBLIC_DIR/images/arrow.right.react.svg?url";
|
||||
|
||||
import { ItemProps } from "../TenantList.types";
|
||||
import { IconButton } from "@docspace/shared/components/icon-button";
|
||||
import { generateOAuth2ReferenceURl } from "@/utils";
|
||||
|
||||
const Item = ({ clientId, portal, baseDomain }: ItemProps) => {
|
||||
console.log(portal);
|
||||
const name = portal.portalName.includes(baseDomain)
|
||||
? portal.portalName
|
||||
: `${portal.portalName}.${baseDomain}`;
|
||||
|
||||
const onClick = () => {
|
||||
const referenceUrl = generateOAuth2ReferenceURl(clientId);
|
||||
|
||||
window.open(`${portal.portalLink}&referenceUrl=${referenceUrl}`, "_self");
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="item" onClick={onClick}>
|
||||
<div className="info">
|
||||
<img
|
||||
className="favicon"
|
||||
alt="Portal favicon"
|
||||
src={`${name}/logo.ashx?logotype=3`}
|
||||
/>
|
||||
<Text fontWeight={600} fontSize="14px" lineHeight="16px" truncate>
|
||||
{name.replace("http://", "").replace("https://", "")}
|
||||
</Text>
|
||||
</div>
|
||||
<IconButton
|
||||
iconName={ArrowRightSvrUrl}
|
||||
size={16}
|
||||
className="icon-button"
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Item;
|
@ -40,29 +40,26 @@ export function middleware(request: NextRequest) {
|
||||
}
|
||||
|
||||
const isAuth = !!request.cookies.get("asc_auth_key")?.value;
|
||||
|
||||
const isOAuth = request.nextUrl.searchParams.get("type") === "oauth2";
|
||||
|
||||
if (isOAuth) {
|
||||
const oauthClientId =
|
||||
request.nextUrl.searchParams.get("client_id") ??
|
||||
request.nextUrl.searchParams.get("clientId");
|
||||
|
||||
const oauthClientId =
|
||||
request.nextUrl.searchParams.get("client_id") ??
|
||||
request.nextUrl.searchParams.get("clientId");
|
||||
if (isOAuth || oauthClientId) {
|
||||
if (oauthClientId === "error")
|
||||
return NextResponse.redirect(`${redirectUrl}/login/error`);
|
||||
|
||||
if (isAuth) {
|
||||
if (request.nextUrl.pathname === "/consent") return;
|
||||
|
||||
if (isAuth && !request.nextUrl.pathname.includes("consent")) {
|
||||
return NextResponse.redirect(
|
||||
`${redirectUrl}/login/consent${request.nextUrl.search}`,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = "/";
|
||||
|
||||
if (isAuth && redirectUrl) return NextResponse.redirect(redirectUrl);
|
||||
}
|
||||
|
||||
const url = request.nextUrl.clone();
|
||||
url.pathname = "/";
|
||||
|
||||
if (isAuth && redirectUrl) return NextResponse.redirect(redirectUrl);
|
||||
}
|
||||
|
||||
// See "Matching Paths" below to learn more
|
||||
|
@ -33,7 +33,7 @@ import {
|
||||
TThirdPartyProvider,
|
||||
} from "@docspace/shared/api/settings/types";
|
||||
import { TValidate } from "@docspace/shared/components/email-input/EmailInput.types";
|
||||
import { INoAuthClientProps } from "@docspace/shared/utils/oauth/interfaces";
|
||||
import { IClientProps } from "@docspace/shared/utils/oauth/types";
|
||||
import { RecaptchaType, ThemeKeys } from "@docspace/shared/enums";
|
||||
|
||||
export type TDataContext = {
|
||||
@ -88,7 +88,7 @@ export type LoginFormProps = {
|
||||
reCaptchaType?: RecaptchaType;
|
||||
cookieSettingsEnabled: boolean;
|
||||
clientId?: string;
|
||||
client?: INoAuthClientProps;
|
||||
client?: IClientProps;
|
||||
};
|
||||
|
||||
export type ForgotPasswordModalDialogProps = {
|
||||
|
@ -42,10 +42,7 @@ import {
|
||||
TThirdPartyProvider,
|
||||
TVersionBuild,
|
||||
} from "@docspace/shared/api/settings/types";
|
||||
import {
|
||||
INoAuthClientProps,
|
||||
IScope,
|
||||
} from "@docspace/shared/utils/oauth/interfaces";
|
||||
import { IScope } from "@docspace/shared/utils/oauth/types";
|
||||
import { transformToClientProps } from "@docspace/shared/utils/oauth";
|
||||
|
||||
export const checkIsAuthenticated = async () => {
|
||||
@ -182,27 +179,9 @@ export async function getScopeList() {
|
||||
return scopes as IScope[];
|
||||
}
|
||||
|
||||
export async function getOAuthClient(clientId: string, isAuth = true) {
|
||||
if (!isAuth) {
|
||||
const [getOAuthClient] = createRequest(
|
||||
[`/clients/${clientId}/info`],
|
||||
[["", ""]],
|
||||
"GET",
|
||||
);
|
||||
|
||||
const oauthClient = await fetch(getOAuthClient);
|
||||
|
||||
console.log(oauthClient);
|
||||
|
||||
if (!oauthClient.ok) return;
|
||||
|
||||
const client = (await oauthClient.json()) as INoAuthClientProps;
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export async function getOAuthClient(clientId: string) {
|
||||
const [getOAuthClient] = createRequest(
|
||||
[`/clients/${clientId}`],
|
||||
[`/clients/${clientId}/public/info`],
|
||||
[["", ""]],
|
||||
"GET",
|
||||
);
|
||||
@ -232,6 +211,30 @@ export async function getPortalCultures() {
|
||||
return cultures.response as TPortalCultures;
|
||||
}
|
||||
|
||||
export async function gitAvailablePortals(data: {
|
||||
email: string;
|
||||
passwordHash: string;
|
||||
}) {
|
||||
const [gitAvailablePortals] = createRequest(
|
||||
[`/portal/signin`],
|
||||
[["Content-Type", "application/json"]],
|
||||
"POST",
|
||||
JSON.stringify(data),
|
||||
true,
|
||||
);
|
||||
|
||||
console.log(gitAvailablePortals.url);
|
||||
|
||||
const response = await fetch(gitAvailablePortals);
|
||||
if (!response.ok) return null;
|
||||
|
||||
const { response: portals } = await response.json();
|
||||
|
||||
console.log(portals);
|
||||
|
||||
// return config;
|
||||
}
|
||||
|
||||
export async function getConfig() {
|
||||
const baseUrl = getBaseUrl();
|
||||
const config = await (
|
||||
|
@ -138,3 +138,7 @@ export const getEmailFromInvitation = (encodeString: Nullable<string>) => {
|
||||
|
||||
return queryParams.email;
|
||||
};
|
||||
|
||||
export const generateOAuth2ReferenceURl = (clientId: string) => {
|
||||
return `/login/consent?clientId=${clientId}`;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user