mirror of
https://github.com/actualbudget/actual.git
synced 2026-03-11 20:44:32 -05:00
Extract auth related server handlers from main.ts to server/auth/app.ts (#4660)
* Extract auth related server handlers from main.ts to server/auth/app.ts * Release notes * Update get-openid-config * Fix typecheck error * Fix lint and typecheck errors
This commit is contained in:
committed by
GitHub
parent
1b5be7f9d2
commit
01f45d5072
@@ -86,7 +86,9 @@ global.Actual = {
|
||||
});
|
||||
},
|
||||
|
||||
startOAuthServer: () => {},
|
||||
startOAuthServer: () => {
|
||||
return '';
|
||||
},
|
||||
|
||||
restartElectronServer: () => {},
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ import { type Handlers } from 'loot-core/types/handlers';
|
||||
|
||||
import { useDispatch } from '../redux';
|
||||
|
||||
type LoginMethods = {
|
||||
type LoginMethod = {
|
||||
method: string;
|
||||
displayName: string;
|
||||
active: boolean;
|
||||
@@ -25,14 +25,14 @@ type ServerContextValue = {
|
||||
url: string | null;
|
||||
version: string;
|
||||
multiuserEnabled: boolean;
|
||||
availableLoginMethods: LoginMethods[];
|
||||
availableLoginMethods: LoginMethod[];
|
||||
setURL: (
|
||||
url: string,
|
||||
opts?: { validate?: boolean },
|
||||
) => Promise<{ error?: string }>;
|
||||
refreshLoginMethods: () => Promise<void>;
|
||||
setMultiuserEnabled: (enabled: boolean) => void;
|
||||
setLoginMethods: (methods: LoginMethods[]) => void;
|
||||
setLoginMethods: (methods: LoginMethod[]) => void;
|
||||
};
|
||||
|
||||
const ServerContext = createContext<ServerContextValue>({
|
||||
@@ -91,7 +91,7 @@ export function ServerProvider({ children }: { children: ReactNode }) {
|
||||
const [version, setVersion] = useState('');
|
||||
const [multiuserEnabled, setMultiuserEnabled] = useState(false);
|
||||
const [availableLoginMethods, setAvailableLoginMethods] = useState<
|
||||
LoginMethods[]
|
||||
LoginMethod[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -134,8 +134,8 @@ export function ServerProvider({ children }: { children: ReactNode }) {
|
||||
send('subscribe-needs-bootstrap').then(
|
||||
(data: Awaited<ReturnType<Handlers['subscribe-needs-bootstrap']>>) => {
|
||||
if ('hasServer' in data && data.hasServer) {
|
||||
setAvailableLoginMethods(data.availableLoginMethods);
|
||||
setMultiuserEnabled(data.multiuser);
|
||||
setAvailableLoginMethods(data.availableLoginMethods || []);
|
||||
setMultiuserEnabled(data.multiuser || false);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
@@ -113,8 +113,8 @@ function OpenIdLogin({ setError }) {
|
||||
}, []);
|
||||
|
||||
async function onSubmitOpenId() {
|
||||
const { error, redirect_url } = await send('subscribe-sign-in', {
|
||||
return_url: isElectron()
|
||||
const { error, redirectUrl } = await send('subscribe-sign-in', {
|
||||
returnUrl: isElectron()
|
||||
? await window.Actual.startOAuthServer()
|
||||
: window.location.origin,
|
||||
loginMethod: 'openid',
|
||||
@@ -125,9 +125,9 @@ function OpenIdLogin({ setError }) {
|
||||
setError(error);
|
||||
} else {
|
||||
if (isElectron()) {
|
||||
window.Actual?.openURLInBrowser(redirect_url);
|
||||
window.Actual?.openURLInBrowser(redirectUrl);
|
||||
} else {
|
||||
window.location.href = redirect_url;
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ export const sync = createAppAsyncThunk(
|
||||
const prefs = getState().prefs.local;
|
||||
if (prefs && prefs.id) {
|
||||
const result = await send('sync');
|
||||
if ('error' in result) {
|
||||
if (result && 'error' in result) {
|
||||
return { error: result.error };
|
||||
}
|
||||
|
||||
|
||||
@@ -322,23 +322,36 @@ async function enableOpenId(openIdConfig: { openId: OpenIdConfig }) {
|
||||
return {};
|
||||
}
|
||||
|
||||
async function getOpenIdConfig() {
|
||||
async function getOpenIdConfig({ password }: { password: string }) {
|
||||
try {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
const serverConfig = getServer();
|
||||
if (!serverConfig) {
|
||||
throw new Error('No sync server configured.');
|
||||
}
|
||||
|
||||
const res = await get(serverConfig.BASE_SERVER + '/openid/config');
|
||||
const res = await post(
|
||||
serverConfig.BASE_SERVER + '/openid/config',
|
||||
{ password },
|
||||
{
|
||||
'X-ACTUAL-TOKEN': userToken,
|
||||
},
|
||||
);
|
||||
|
||||
if (res) {
|
||||
const config = JSON.parse(res) as OpenIdConfig;
|
||||
return { openId: config };
|
||||
return res as { openId: OpenIdConfig };
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
return { error: 'config-fetch-failed' };
|
||||
if (err instanceof PostError) {
|
||||
return {
|
||||
error: err.reason || 'network-failure',
|
||||
};
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,12 +9,12 @@ import * as fs from '../platform/server/fs';
|
||||
import * as sqlite from '../platform/server/sqlite';
|
||||
import { q } from '../shared/query';
|
||||
import { Handlers } from '../types/handlers';
|
||||
import { OpenIdConfig } from '../types/models/openid';
|
||||
|
||||
import { app as accountsApp } from './accounts/app';
|
||||
import { app as adminApp } from './admin/app';
|
||||
import { installAPI } from './api';
|
||||
import { runQuery as aqlQuery } from './aql';
|
||||
import { app as authApp } from './auth/app';
|
||||
import { app as budgetApp } from './budget/app';
|
||||
import { app as budgetFilesApp } from './budgetfiles/app';
|
||||
import { app as dashboardApp } from './dashboard/app';
|
||||
@@ -27,13 +27,13 @@ import { mutator, runHandler } from './mutators';
|
||||
import { app as notesApp } from './notes/app';
|
||||
import { app as payeesApp } from './payees/app';
|
||||
import * as Platform from './platform';
|
||||
import { get, post } from './post';
|
||||
import { get } from './post';
|
||||
import { app as preferencesApp } from './preferences/app';
|
||||
import * as prefs from './prefs';
|
||||
import { app as reportsApp } from './reports/app';
|
||||
import { app as rulesApp } from './rules/app';
|
||||
import { app as schedulesApp } from './schedules/app';
|
||||
import { getServer, isValidBaseURL, setServer } from './server-config';
|
||||
import { getServer, setServer } from './server-config';
|
||||
import { app as spreadsheetApp } from './spreadsheet/app';
|
||||
import { fullSync, setSyncingMode } from './sync';
|
||||
import { app as syncApp } from './sync/app';
|
||||
@@ -71,195 +71,6 @@ handlers['query'] = async function (query) {
|
||||
return aqlQuery(query);
|
||||
};
|
||||
|
||||
handlers['get-did-bootstrap'] = async function () {
|
||||
return Boolean(await asyncStorage.getItem('did-bootstrap'));
|
||||
};
|
||||
|
||||
handlers['subscribe-needs-bootstrap'] = async function ({
|
||||
url,
|
||||
}: { url? } = {}) {
|
||||
if (url && !isValidBaseURL(url)) {
|
||||
return { error: 'get-server-failure' };
|
||||
}
|
||||
|
||||
try {
|
||||
if (!getServer(url)) {
|
||||
return { bootstrapped: true, hasServer: false };
|
||||
}
|
||||
} catch (err) {
|
||||
return { error: 'get-server-failure' };
|
||||
}
|
||||
|
||||
let res;
|
||||
try {
|
||||
res = await get(getServer(url).SIGNUP_SERVER + '/needs-bootstrap');
|
||||
} catch (err) {
|
||||
return { error: 'network-failure' };
|
||||
}
|
||||
|
||||
try {
|
||||
res = JSON.parse(res);
|
||||
} catch (err) {
|
||||
return { error: 'parse-failure' };
|
||||
}
|
||||
|
||||
if (res.status === 'error') {
|
||||
return { error: res.reason };
|
||||
}
|
||||
|
||||
return {
|
||||
bootstrapped: res.data.bootstrapped,
|
||||
availableLoginMethods: res.data.availableLoginMethods || [
|
||||
{ method: 'password', active: true, displayName: 'Password' },
|
||||
],
|
||||
multiuser: res.data.multiuser || false,
|
||||
hasServer: true,
|
||||
};
|
||||
};
|
||||
|
||||
handlers['subscribe-bootstrap'] = async function (loginConfig) {
|
||||
try {
|
||||
await post(getServer().SIGNUP_SERVER + '/bootstrap', loginConfig);
|
||||
} catch (err) {
|
||||
return { error: err.reason || 'network-failure' };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
handlers['subscribe-get-login-methods'] = async function () {
|
||||
let res;
|
||||
try {
|
||||
res = await fetch(getServer().SIGNUP_SERVER + '/login-methods').then(res =>
|
||||
res.json(),
|
||||
);
|
||||
} catch (err) {
|
||||
return { error: err.reason || 'network-failure' };
|
||||
}
|
||||
|
||||
if (res.methods) {
|
||||
return { methods: res.methods };
|
||||
}
|
||||
return { error: 'internal' };
|
||||
};
|
||||
|
||||
handlers['subscribe-get-user'] = async function () {
|
||||
if (!getServer()) {
|
||||
if (!(await asyncStorage.getItem('did-bootstrap'))) {
|
||||
return null;
|
||||
}
|
||||
return { offline: false };
|
||||
}
|
||||
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
if (!userToken) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await get(getServer().SIGNUP_SERVER + '/validate', {
|
||||
headers: {
|
||||
'X-ACTUAL-TOKEN': userToken,
|
||||
},
|
||||
});
|
||||
let tokenExpired = false;
|
||||
const {
|
||||
status,
|
||||
reason,
|
||||
data: {
|
||||
userName = null,
|
||||
permission = '',
|
||||
userId = null,
|
||||
displayName = null,
|
||||
loginMethod = null,
|
||||
} = {},
|
||||
} = JSON.parse(res) || {};
|
||||
|
||||
if (status === 'error') {
|
||||
if (reason === 'unauthorized') {
|
||||
return null;
|
||||
} else if (reason === 'token-expired') {
|
||||
tokenExpired = true;
|
||||
} else {
|
||||
return { offline: true };
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
offline: false,
|
||||
userName,
|
||||
permission,
|
||||
userId,
|
||||
displayName,
|
||||
loginMethod,
|
||||
tokenExpired,
|
||||
};
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
return { offline: true };
|
||||
}
|
||||
};
|
||||
|
||||
handlers['subscribe-change-password'] = async function ({ password }) {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
if (!userToken) {
|
||||
return { error: 'not-logged-in' };
|
||||
}
|
||||
|
||||
try {
|
||||
await post(getServer().SIGNUP_SERVER + '/change-password', {
|
||||
token: userToken,
|
||||
password,
|
||||
});
|
||||
} catch (err) {
|
||||
return { error: err.reason || 'network-failure' };
|
||||
}
|
||||
|
||||
return {};
|
||||
};
|
||||
|
||||
handlers['subscribe-sign-in'] = async function (loginInfo) {
|
||||
if (
|
||||
typeof loginInfo.loginMethod !== 'string' ||
|
||||
loginInfo.loginMethod == null
|
||||
) {
|
||||
loginInfo.loginMethod = 'password';
|
||||
}
|
||||
let res;
|
||||
|
||||
try {
|
||||
res = await post(getServer().SIGNUP_SERVER + '/login', loginInfo);
|
||||
} catch (err) {
|
||||
return { error: err.reason || 'network-failure' };
|
||||
}
|
||||
|
||||
if (res.redirect_url) {
|
||||
return { redirect_url: res.redirect_url };
|
||||
}
|
||||
|
||||
if (!res.token) {
|
||||
throw new Error('login: User token not set');
|
||||
}
|
||||
|
||||
await asyncStorage.setItem('user-token', res.token);
|
||||
return {};
|
||||
};
|
||||
|
||||
handlers['subscribe-sign-out'] = async function () {
|
||||
encryption.unloadAllKeys();
|
||||
await asyncStorage.multiRemove([
|
||||
'user-token',
|
||||
'encrypt-keys',
|
||||
'lastBudget',
|
||||
'readOnly',
|
||||
]);
|
||||
return 'ok';
|
||||
};
|
||||
|
||||
handlers['subscribe-set-token'] = async function ({ token }) {
|
||||
await asyncStorage.setItem('user-token', token);
|
||||
};
|
||||
|
||||
handlers['get-server-version'] = async function () {
|
||||
if (!getServer()) {
|
||||
return { error: 'no-server' };
|
||||
@@ -305,111 +116,6 @@ handlers['set-server-url'] = async function ({ url, validate = true }) {
|
||||
return {};
|
||||
};
|
||||
|
||||
handlers['enable-openid'] = async function (loginConfig) {
|
||||
try {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
if (!userToken) {
|
||||
return { error: 'unauthorized' };
|
||||
}
|
||||
|
||||
await post(getServer().BASE_SERVER + '/openid/enable', loginConfig, {
|
||||
'X-ACTUAL-TOKEN': userToken,
|
||||
});
|
||||
} catch (err) {
|
||||
return { error: err.reason || 'network-failure' };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
handlers['enable-password'] = async function (loginConfig) {
|
||||
try {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
if (!userToken) {
|
||||
return { error: 'unauthorized' };
|
||||
}
|
||||
|
||||
await post(getServer().BASE_SERVER + '/openid/disable', loginConfig, {
|
||||
'X-ACTUAL-TOKEN': userToken,
|
||||
});
|
||||
} catch (err) {
|
||||
return { error: err.reason || 'network-failure' };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
handlers['get-openid-config'] = async function () {
|
||||
try {
|
||||
const res = await get(getServer().BASE_SERVER + '/openid/config');
|
||||
|
||||
if (res) {
|
||||
const config = JSON.parse(res) as OpenIdConfig;
|
||||
return { openId: config };
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
return { error: 'config-fetch-failed' };
|
||||
}
|
||||
};
|
||||
|
||||
handlers['enable-openid'] = async function (loginConfig) {
|
||||
try {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
if (!userToken) {
|
||||
return { error: 'unauthorized' };
|
||||
}
|
||||
|
||||
await post(getServer().BASE_SERVER + '/openid/enable', loginConfig, {
|
||||
'X-ACTUAL-TOKEN': userToken,
|
||||
});
|
||||
} catch (err) {
|
||||
return { error: err.reason || 'network-failure' };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
handlers['enable-password'] = async function (loginConfig) {
|
||||
try {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
if (!userToken) {
|
||||
return { error: 'unauthorized' };
|
||||
}
|
||||
|
||||
await post(getServer().BASE_SERVER + '/openid/disable', loginConfig, {
|
||||
'X-ACTUAL-TOKEN': userToken,
|
||||
});
|
||||
} catch (err) {
|
||||
return { error: err.reason || 'network-failure' };
|
||||
}
|
||||
return {};
|
||||
};
|
||||
|
||||
handlers['get-openid-config'] = async function ({ password }) {
|
||||
try {
|
||||
const userToken = await asyncStorage.getItem('user-token');
|
||||
|
||||
const res = await post(
|
||||
getServer().BASE_SERVER + '/openid/config',
|
||||
{ password },
|
||||
{
|
||||
'X-ACTUAL-TOKEN': userToken,
|
||||
},
|
||||
);
|
||||
|
||||
if (res) {
|
||||
return res as { openId: OpenIdConfig };
|
||||
}
|
||||
|
||||
return null;
|
||||
} catch (err) {
|
||||
return { error: err.reason };
|
||||
}
|
||||
};
|
||||
|
||||
handlers['app-focused'] = async function () {
|
||||
if (prefs.getPrefs() && prefs.getPrefs().id) {
|
||||
// First we sync
|
||||
@@ -424,6 +130,7 @@ injectAPI.override((name, args) => runHandler(app.handlers[name], args));
|
||||
// A hack for now until we clean up everything
|
||||
app.handlers = handlers;
|
||||
app.combine(
|
||||
authApp,
|
||||
schedulesApp,
|
||||
budgetApp,
|
||||
dashboardApp,
|
||||
|
||||
4
packages/loot-core/src/types/handlers.d.ts
vendored
4
packages/loot-core/src/types/handlers.d.ts
vendored
@@ -1,5 +1,6 @@
|
||||
import type { AccountHandlers } from '../server/accounts/app';
|
||||
import type { AdminHandlers } from '../server/admin/app';
|
||||
import type { AuthHandlers } from '../server/auth/app';
|
||||
import type { BudgetHandlers } from '../server/budget/app';
|
||||
import type { BudgetFileHandlers } from '../server/budgetfiles/app';
|
||||
import type { DashboardHandlers } from '../server/dashboard/app';
|
||||
@@ -38,6 +39,7 @@ export interface Handlers
|
||||
SpreadsheetHandlers,
|
||||
SyncHandlers,
|
||||
BudgetFileHandlers,
|
||||
EncryptionHandlers {}
|
||||
EncryptionHandlers,
|
||||
AuthHandlers {}
|
||||
|
||||
export type HandlerFunctions = Handlers[keyof Handlers];
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
import { Message } from '../server/sync';
|
||||
import { QueryState } from '../shared/query';
|
||||
|
||||
import { OpenIdConfig } from './models/openid';
|
||||
|
||||
export interface ServerHandlers {
|
||||
undo: () => Promise<void>;
|
||||
redo: () => Promise<void>;
|
||||
@@ -15,66 +12,6 @@ export interface ServerHandlers {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
query: (query: QueryState) => Promise<{ data: any; dependencies: string[] }>;
|
||||
|
||||
'get-did-bootstrap': () => Promise<boolean>;
|
||||
|
||||
'subscribe-needs-bootstrap': (args: { url }) => Promise<
|
||||
| { error: string }
|
||||
| {
|
||||
bootstrapped: boolean;
|
||||
hasServer: false;
|
||||
}
|
||||
| {
|
||||
bootstrapped: boolean;
|
||||
hasServer: true;
|
||||
availableLoginMethods: {
|
||||
method: string;
|
||||
displayName: string;
|
||||
active: boolean;
|
||||
}[];
|
||||
multiuser: boolean;
|
||||
}
|
||||
>;
|
||||
|
||||
'subscribe-get-login-methods': () => Promise<{
|
||||
methods?: { method: string; displayName: string; active: boolean }[];
|
||||
error?: string;
|
||||
}>;
|
||||
|
||||
'subscribe-bootstrap': (arg: {
|
||||
password?: string;
|
||||
openId?: OpenIdConfig;
|
||||
}) => Promise<{ error?: string }>;
|
||||
|
||||
'subscribe-get-user': () => Promise<{
|
||||
offline: boolean;
|
||||
userName?: string;
|
||||
userId?: string;
|
||||
displayName?: string;
|
||||
permission?: string;
|
||||
loginMethod?: string;
|
||||
tokenExpired?: boolean;
|
||||
} | null>;
|
||||
|
||||
'subscribe-change-password': (arg: {
|
||||
password;
|
||||
}) => Promise<{ error?: string }>;
|
||||
|
||||
'subscribe-sign-in': (
|
||||
arg:
|
||||
| {
|
||||
password;
|
||||
loginMethod?: string;
|
||||
}
|
||||
| {
|
||||
return_url;
|
||||
loginMethod?: 'openid';
|
||||
},
|
||||
) => Promise<{ error?: string; redirect_url?: string }>;
|
||||
|
||||
'subscribe-sign-out': () => Promise<'ok'>;
|
||||
|
||||
'subscribe-set-token': (arg: { token: string }) => Promise<void>;
|
||||
|
||||
'get-server-version': () => Promise<{ error?: string } | { version: string }>;
|
||||
|
||||
'get-server-url': () => Promise<string | null>;
|
||||
@@ -84,24 +21,5 @@ export interface ServerHandlers {
|
||||
validate?: boolean;
|
||||
}) => Promise<{ error?: string }>;
|
||||
|
||||
sync: () => Promise<
|
||||
| { error: { message: string; reason: string; meta: unknown } }
|
||||
| { messages: Message[] }
|
||||
>;
|
||||
|
||||
'app-focused': () => Promise<void>;
|
||||
|
||||
'enable-openid': (arg: {
|
||||
openId?: OpenIdConfig;
|
||||
}) => Promise<{ error?: string }>;
|
||||
|
||||
'enable-password': (arg: { password: string }) => Promise<{ error?: string }>;
|
||||
|
||||
'get-openid-config': (arg: { password: string }) => Promise<
|
||||
| {
|
||||
openId: OpenIdConfig;
|
||||
}
|
||||
| { error: string }
|
||||
| null
|
||||
>;
|
||||
}
|
||||
|
||||
2
packages/loot-core/typings/window.d.ts
vendored
2
packages/loot-core/typings/window.d.ts
vendored
@@ -29,7 +29,7 @@ type Actual = {
|
||||
) => void;
|
||||
isUpdateReadyForDownload: () => boolean;
|
||||
waitForUpdateReadyForDownload: () => Promise<void>;
|
||||
startOAuthServer: () => Promise<void>;
|
||||
startOAuthServer: () => Promise<string>;
|
||||
};
|
||||
|
||||
declare global {
|
||||
|
||||
6
upcoming-release-notes/4660.md
Normal file
6
upcoming-release-notes/4660.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
category: Maintenance
|
||||
authors: [joel-jeremy]
|
||||
---
|
||||
|
||||
Extract auth related server handlers from main.ts to server/auth/app.ts
|
||||
Reference in New Issue
Block a user