Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 131 additions & 48 deletions apps/client/src/screens/connect/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { getFileUrl, getUrlFromServer } from '@/helpers/get-file-url';
import {
getLocalStorageItem,
getLocalStorageItemBool,
getSessionStorageItem,
LocalStorageKey,
removeLocalStorageItem,
SessionStorageKey,
Expand All @@ -14,6 +15,7 @@ import {
setSessionStorageItem
} from '@/helpers/storage';
import { useForm } from '@/hooks/use-form';
import { useStrictEffect } from '@/hooks/use-strict-effect';
import { PluginSlot, TestId } from '@sharkord/shared';
import {
Alert,
Expand Down Expand Up @@ -58,6 +60,77 @@ const Connect = memo(() => {
return invite || undefined;
}, []);

const onRememberCredentialsChange = useCallback(
(checked: boolean) => {
onChange('rememberCredentials', checked);

if (checked) {
setLocalStorageItem(LocalStorageKey.REMEMBER_CREDENTIALS, 'true');
} else {
removeLocalStorageItem(LocalStorageKey.REMEMBER_CREDENTIALS);
}
},
[onChange]
);

const onOidcLoginClick = useCallback(() => {
const url = getUrlFromServer();
window.location.href = `${url}/auth/login`;
}, []);

const handleOidcSuccess = useCallback(async () => {
setLoading(true);
try {
const cookies = document.cookie.split('; ').reduce(
(acc, current) => {
const [name, value] = current.split('=');
acc[name] = value;
return acc;
},
{} as Record<string, string>
);

const token = cookies['sharkord_token'];

if (token) {
setSessionStorageItem(SessionStorageKey.TOKEN, token);

document.cookie =
'sharkord_token=; Max-Age=0; path=/; SameSite=Lax; Secure';
} else {
throw new Error('No authentication token found in cookies.');
}

await connect();
toast.success('Logged in with OIDC');
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
toast.error(`Could not connect with OIDC: ${errorMessage}`);
} finally {
setLoading(false);
}
}, []);

useStrictEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
const oidcStatus = urlParams.get('oidc_status');

if (oidcStatus === 'success') {
const newUrl = window.location.pathname;
window.history.replaceState({}, document.title, newUrl);

handleOidcSuccess();
}
}, [handleOidcSuccess]);

useStrictEffect(() => {
const token = getSessionStorageItem(SessionStorageKey.TOKEN);
if (info && info.oidcEnabled && !info.allowNewUsers && !token) {
onOidcLoginClick();
}
Comment on lines +127 to +131
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Don’t couple OIDC-only login behavior to allowNewUsers.

Line 122 uses signup policy (allowNewUsers) to force OIDC redirect. This also suppresses local credential login in the UI (Lines 209 and 268), which can lock out existing local accounts.

}, [info, onOidcLoginClick]);

const onConnectClick = useCallback(async () => {
setLoading(true);

Expand Down Expand Up @@ -121,10 +194,7 @@ const Connect = memo(() => {
}, [info]);

return (
<div className="flex flex-col gap-2 justify-center items-center h-full relative">
<div className="fixed bottom-4 right-4 sm:bottom-6 sm:right-6 z-50">
<LanguageSwitcher variant="icon" />
</div>
<div className="flex flex-col gap-2 justify-center items-center h-full">
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle className="flex flex-col items-center gap-2 text-center">
Expand All @@ -148,30 +218,32 @@ const Connect = memo(() => {
</span>
)}

<form
className="flex flex-col gap-2"
onSubmit={(e) => {
e.preventDefault();
onConnectClick();
}}
>
<Group label={t('identityLabel')} help={t('identityHelp')}>
<Input
{...r('identity')}
autoComplete="username"
data-testid={TestId.CONNECT_IDENTITY_INPUT}
/>
</Group>
<Group label={t('passwordLabel')}>
<Input
{...r('password')}
type="password"
autoComplete="current-password"
onEnter={onConnectClick}
data-testid={TestId.CONNECT_PASSWORD_INPUT}
/>
</Group>
</form>
{!(info?.oidcEnabled && !info?.allowNewUsers) && (
<form
className="flex flex-col gap-2"
onSubmit={(e) => {
e.preventDefault();
onConnectClick();
}}
>
<Group label={t('identityLabel')} help={t('identityHelp')}>
<Input
{...r('identity')}
autoComplete="username"
data-testid={TestId.CONNECT_IDENTITY_INPUT}
/>
</Group>
<Group label={t('passwordLabel')}>
<Input
{...r('password')}
type="password"
autoComplete="current-password"
onEnter={onConnectClick}
data-testid={TestId.CONNECT_PASSWORD_INPUT}
/>
</Group>
</form>
)}

<div
className="flex items-center gap-2 w-fit cursor-pointer"
Expand All @@ -194,24 +266,33 @@ const Connect = memo(() => {
</Alert>
)}

<Button
className="w-full"
variant="outline"
onClick={onConnectClick}
disabled={loading || !values.identity || !values.password}
data-testid={TestId.CONNECT_BUTTON}
>
{t('connectBtn')}
</Button>

{!info?.allowNewUsers && (
<>
{!inviteCode && (
<span className="text-xs text-muted-foreground text-center">
{t('registrationDisabled')}
</span>
)}
</>
{!(info?.oidcEnabled && !info?.allowNewUsers) && (
<Button
className="w-full"
variant="outline"
onClick={onConnectClick}
disabled={loading || !values.identity || !values.password}
data-testid={TestId.CONNECT_BUTTON}
>
{t('connectBtn')}
</Button>
)}

{info?.oidcEnabled && (
<Button
className="w-full"
variant="secondary"
onClick={onOidcLoginClick}
disabled={loading}
>
Login with OIDC
</Button>
)}

{!info?.allowNewUsers && !inviteCode && (
<span className="text-xs text-muted-foreground text-center">
{t('registrationDisabled')}
</span>
)}

{inviteCode && (
Expand All @@ -228,8 +309,8 @@ const Connect = memo(() => {
</CardContent>
</Card>

<div className="flex justify-center items-center gap-2 text-xs text-muted-foreground select-none">
<span>v{VITE_APP_VERSION}</span>
<div className="flex justify-center gap-2 text-xs text-muted-foreground select-none">
<span>v{import.meta.env.VITE_APP_VERSION}</span>
<a
href="https://github.com/sharkord/sharkord"
target="_blank"
Expand All @@ -246,6 +327,8 @@ const Connect = memo(() => {
>
Sharkord
</a>

<LanguageSwitcher variant="icon" />
</div>
</div>
);
Expand Down
1 change: 1 addition & 0 deletions apps/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"jsonwebtoken": "^9.0.2",
"link-preview-js": "^3.1.0",
"mediasoup": "^3.19.17",
"openid-client": "^6.8.2",
"queue": "^7.0.0",
"sanitize-html": "^2.17.0",
"semver": "^7.7.3",
Expand Down
Loading
Loading