Skip to content
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

OpenId Implementation #3878

Open
wants to merge 58 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
5a65c68
OpenId implementation
lelemm Nov 22, 2024
45e855e
Code rabbit auto generated code applied
lelemm Nov 23, 2024
29458e1
Code rabbit suggestions round 2
lelemm Nov 23, 2024
0a7ab28
fixes from code rabbit round 1
lelemm Nov 23, 2024
a60768c
Merge branch 'openid_squashed' of https://github.com/lelemm/actual in…
lelemm Nov 23, 2024
161a89d
fixes from code rabbit round 2
lelemm Nov 23, 2024
247626e
change variable name
lelemm Nov 25, 2024
75384b0
code review round 3
lelemm Nov 25, 2024
cfc94ab
Update VRT
github-actions[bot] Nov 25, 2024
cbbe12b
Merge branch 'master' into openid_squashed
lelemm Nov 25, 2024
e9fe496
small fix
lelemm Nov 28, 2024
1649677
Update VRT
github-actions[bot] Nov 28, 2024
b8c3968
Merge branch 'master' into openid_squashed
lelemm Nov 28, 2024
b16d75b
Merge remote-tracking branch 'org/master' into openid_squashed
lelemm Dec 9, 2024
7145727
linter
lelemm Dec 9, 2024
226715f
app.tsx
matt-fidd Dec 11, 2024
4d2b1d8
LoggedInUser
matt-fidd Dec 11, 2024
6198dbf
UserAccess
matt-fidd Dec 11, 2024
495ea07
UserAccessHeader
matt-fidd Dec 11, 2024
8cefd95
UserAccessPage
matt-fidd Dec 11, 2024
6f99dcc
UserAccessRow
matt-fidd Dec 11, 2024
38dd40f
UserDirectory
matt-fidd Dec 11, 2024
e4f5f87
UserDirectoryHeader
matt-fidd Dec 11, 2024
0429f03
UserDirectoryPage
matt-fidd Dec 11, 2024
972f5d8
UserDirectoryRow
matt-fidd Dec 11, 2024
21fbf5c
BudgetList
matt-fidd Dec 11, 2024
f5094ee
Bootstrap
matt-fidd Dec 11, 2024
c1e74d7
Login
matt-fidd Dec 11, 2024
4e0cbc5
OpenIdForm
matt-fidd Dec 11, 2024
44cf6fe
CreateAccountModal
matt-fidd Dec 11, 2024
b3f8be8
EditAccess
matt-fidd Dec 11, 2024
eb1599f
EditUser
matt-fidd Dec 11, 2024
f97c023
GoCardlessInitialiseModal
matt-fidd Dec 11, 2024
f6c9abf
OpenIDEnableModal
matt-fidd Dec 11, 2024
79cd3a1
PasswordEnableModal
matt-fidd Dec 11, 2024
7ca7c9a
SimpleFinInitialiseModal
matt-fidd Dec 11, 2024
29e8c44
TransferOwnership
matt-fidd Dec 11, 2024
fde7ec6
AuthSettings
matt-fidd Dec 11, 2024
21a3817
fix hooks in EditUser
matt-fidd Dec 12, 2024
0ef78e9
Merge pull request #2 from matt-fidd/openid_squashed
lelemm Dec 13, 2024
2ed6f88
enable electron openid login
lelemm Dec 13, 2024
e825acf
Merge branch 'openid_squashed' of https://github.com/lelemm/actual in…
lelemm Dec 13, 2024
de9ca5a
Merge remote-tracking branch 'org/master' into openid_squashed
lelemm Dec 13, 2024
2153c55
typecheck
lelemm Dec 13, 2024
7aa6e6e
linter and typecheck fixes
lelemm Dec 13, 2024
c7eae8f
Update VRT
github-actions[bot] Dec 13, 2024
3a738df
small fix
lelemm Dec 16, 2024
d8b995e
linter
lelemm Dec 16, 2024
3422a37
small changes for file owner name and a fix for privacyfilter in the …
lelemm Dec 16, 2024
d6ea91d
Merge remote-tracking branch 'org/master' into openid_squashed
lelemm Dec 16, 2024
45f89b2
linter for merge
lelemm Dec 16, 2024
cf53879
change the entra url and changing the electron loopback url when built
lelemm Dec 16, 2024
31a6a23
Merge branch 'master' into openid_squashed
lelemm Dec 16, 2024
d474f8e
"logged in as" was showing when had no user
lelemm Dec 16, 2024
ba1d9fd
Merge branch 'openid_squashed' of https://github.com/lelemm/actual in…
lelemm Dec 16, 2024
0c4e01e
linter
lelemm Dec 16, 2024
73cf70b
linter²
lelemm Dec 17, 2024
418f1ce
code review
lelemm Dec 21, 2024
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
5 changes: 4 additions & 1 deletion packages/api/methods.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,10 @@ export function addTransactions(
}

export function importTransactions(accountId, transactions) {
return send('api/transactions-import', { accountId, transactions });
return send('api/transactions-import', {
accountId,
transactions,
});
}

export function getTransactions(accountId, startDate, endDate) {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
48 changes: 48 additions & 0 deletions packages/desktop-client/src/auth/AuthProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import React, { createContext, useContext, type ReactNode } from 'react';
import { useSelector } from 'react-redux';

import { type State } from 'loot-core/client/state-types';

import { useServerURL } from '../components/ServerContext';

import { type Permissions } from './types';

type AuthContextType = {
hasPermission: (permission?: Permissions) => boolean;
};

const AuthContext = createContext<AuthContextType | undefined>(undefined);

type AuthProviderProps = {
children?: ReactNode;
};

export const AuthProvider = ({ children }: AuthProviderProps) => {
const userData = useSelector((state: State) => state.user.data);
const serverUrl = useServerURL();

const hasPermission = (permission?: Permissions) => {
if (!permission) {
return true;
}

return (
!serverUrl ||
userData?.permission?.toUpperCase() === permission?.toUpperCase()
);
};

return (
<AuthContext.Provider value={{ hasPermission }}>
{children}
</AuthContext.Provider>
);
};

export const useAuth = () => {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
};
65 changes: 65 additions & 0 deletions packages/desktop-client/src/auth/ProtectedRoute.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useEffect, useState, type ReactElement } from 'react';
import { useSelector } from 'react-redux';

import { type RemoteFile, type SyncedLocalFile } from 'loot-core/types/file';

import { View } from '../components/common/View';
import { useMetadataPref } from '../hooks/useMetadataPref';

import { useAuth } from './AuthProvider';
import { type Permissions } from './types';

type ProtectedRouteProps = {
permission: Permissions;
element: ReactElement;
validateOwner?: boolean;
};

export const ProtectedRoute = ({
element,
permission,
validateOwner,
}: ProtectedRouteProps) => {
const { hasPermission } = useAuth();
const [permissionGranted, setPermissionGranted] = useState(false);
const [cloudFileId] = useMetadataPref('cloudFileId');
const allFiles = useSelector(state => state.budgets.allFiles || []);
const remoteFiles = allFiles.filter(
(f): f is SyncedLocalFile | RemoteFile =>
f.state === 'remote' || f.state === 'synced' || f.state === 'detached',
);
const currentFile = remoteFiles.find(f => f.cloudFileId === cloudFileId);
const userData = useSelector(state => state.user.data);

useEffect(() => {
const hasRequiredPermission = hasPermission(permission);
setPermissionGranted(hasRequiredPermission);

if (!hasRequiredPermission && validateOwner) {
if (currentFile) {
setPermissionGranted(
currentFile.usersWithAccess.some(u => u.userId === userData?.userId),
);
}
}
}, [
cloudFileId,
permission,
validateOwner,
hasPermission,
currentFile,
userData,
]);

return permissionGranted ? (
element
) : (
<View
style={{
margin: '50px',
}}
>
<h3>You don&apos;t have permission to view this page</h3>
</View>
);
};
3 changes: 3 additions & 0 deletions packages/desktop-client/src/auth/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export enum Permissions {
ADMINISTRATOR = 'ADMIN',
}
2 changes: 2 additions & 0 deletions packages/desktop-client/src/browser-preload.browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ global.Actual = {
});
},

startOAuthServer: () => {},

restartElectronServer: () => {},

openFileDialog: async ({ filters = [] }) => {
Expand Down
22 changes: 21 additions & 1 deletion packages/desktop-client/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
} from 'react-error-boundary';
import { HotkeysProvider } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next';
import { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';

import {
Expand All @@ -20,12 +20,14 @@ import {
sync,
} from 'loot-core/client/actions';
import { SpreadsheetProvider } from 'loot-core/client/SpreadsheetProvider';
import { type State } from 'loot-core/client/state-types';
import * as Platform from 'loot-core/src/client/platform';
import {
init as initConnection,
send,
} from 'loot-core/src/platform/client/fetch';

import { useActions } from '../hooks/useActions';
import { useMetadataPref } from '../hooks/useMetadataPref';
import { installPolyfills } from '../polyfills';
import { styles, hasHiddenScrollbars, ThemeStyle, useTheme } from '../style';
Expand All @@ -49,6 +51,8 @@ function AppInner() {
const { t } = useTranslation();
const { showBoundary: showErrorBoundary } = useErrorBoundary();
const dispatch = useDispatch();
const userData = useSelector((state: State) => state.user.data);
const { signOut, addNotification } = useActions();

const maybeUpdate = async <T,>(cb?: () => T): Promise<T> => {
if (global.Actual.isUpdateReadyForDownload()) {
Expand Down Expand Up @@ -123,6 +127,22 @@ function AppInner() {
global.Actual.updateAppMenu(budgetId);
}, [budgetId]);

useEffect(() => {
if (userData?.tokenExpired) {
addNotification({
type: 'error',
id: 'login-expired',
title: t('Login expired'),
sticky: true,
message: t('Login expired, please login again.'),
button: {
title: t('Go to login'),
action: signOut,
},
});
}
}, [userData, userData?.tokenExpired]);

return budgetId ? <FinancesApp /> : <ManagementApp />;
}

Expand Down
31 changes: 30 additions & 1 deletion packages/desktop-client/src/components/FinancesApp.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ import { addNotification, sync } from 'loot-core/client/actions';
import { type State } from 'loot-core/src/client/state-types';
import * as undo from 'loot-core/src/platform/client/undo';

import { ProtectedRoute } from '../auth/ProtectedRoute';
import { Permissions } from '../auth/types';
import { useAccounts } from '../hooks/useAccounts';
import { useLocalPref } from '../hooks/useLocalPref';
import { useMetaThemeColor } from '../hooks/useMetaThemeColor';
import { useNavigate } from '../hooks/useNavigate';
import { theme } from '../style';
import { getIsOutdated, getLatestVersion } from '../util/versions';

import { UserAccessPage } from './admin/UserAccess/UserAccessPage';
import { BankSyncStatus } from './BankSyncStatus';
import { View } from './common/View';
import { GlobalKeys } from './GlobalKeys';
Expand All @@ -34,7 +37,9 @@ import { Reports } from './reports';
import { LoadingIndicator } from './reports/LoadingIndicator';
import { NarrowAlternate, WideComponent } from './responsive';
import { useResponsive } from './responsive/ResponsiveProvider';
import { UserDirectoryPage } from './responsive/wide';
import { ScrollProvider } from './ScrollProvider';
import { useMultiuserEnabled } from './ServerContext';
import { Settings } from './settings';
import { FloatableSidebar } from './sidebar';
import { Titlebar } from './Titlebar';
Expand Down Expand Up @@ -93,6 +98,8 @@ export function FinancesApp() {
'flags.updateNotificationShownForVersion',
);

const multiuserEnabled = useMultiuserEnabled();

useEffect(() => {
// Wait a little bit to make sure the sync button will get the
// sync start event. This can be improved later.
Expand Down Expand Up @@ -281,7 +288,29 @@ export function FinancesApp() {
</WideNotSupported>
}
/>

{multiuserEnabled && (
<Route
path="/user-directory"
element={
<ProtectedRoute
permission={Permissions.ADMINISTRATOR}
element={<UserDirectoryPage />}
/>
}
/>
)}
{multiuserEnabled && (
<Route
path="/user-access"
element={
<ProtectedRoute
permission={Permissions.ADMINISTRATOR}
validateOwner={true}
element={<UserAccessPage />}
/>
}
/>
)}
{/* redirect all other traffic to the budget page */}
<Route path="/*" element={<Navigate to="/budget" replace />} />
</Routes>
Expand Down
Loading
Loading