diff --git a/client/src/javascript/actions/AuthActions.ts b/client/src/javascript/actions/AuthActions.ts index 45d89c26..7ab24662 100644 --- a/client/src/javascript/actions/AuthActions.ts +++ b/client/src/javascript/actions/AuthActions.ts @@ -1,4 +1,4 @@ -import axios, {AxiosError} from 'axios'; +import axios, {AxiosError, AxiosResponse} from 'axios'; import type { AuthAuthenticationOptions, @@ -109,7 +109,20 @@ const AuthActions = { verify: () => axios .get(`${baseURI}api/auth/verify?${Date.now()}`) - .then((json) => json.data) + .then( + (res: AxiosResponse) => { + if (res.data.configs != null) { + ConfigStore.handlePreloadConfigs(res.data.configs); + } + return res.data; + }, + (error: AxiosError) => { + if (error.response?.data?.configs != null) { + ConfigStore.handlePreloadConfigs(error.response.data.configs); + } + throw error; + }, + ) .then( (data: AuthVerificationResponse) => { AuthStore.handleAuthVerificationSuccess(data); diff --git a/client/src/javascript/stores/AuthStore.ts b/client/src/javascript/stores/AuthStore.ts index 9bf2572a..7d0cbbc3 100644 --- a/client/src/javascript/stores/AuthStore.ts +++ b/client/src/javascript/stores/AuthStore.ts @@ -5,7 +5,6 @@ import {AccessLevel} from '@shared/schema/Auth'; import type {AuthAuthenticationResponse, AuthVerificationResponse} from '@shared/schema/api/auth'; import type {Credentials} from '@shared/schema/Auth'; -import ConfigStore from './ConfigStore'; import FloodActions from '../actions/FloodActions'; class AuthStore { @@ -67,11 +66,6 @@ class AuthStore { isAdmin: response.level === AccessLevel.ADMINISTRATOR, isInitialUser: response.initialUser, }; - - if (response.token != null) { - // Auth is disabled if a token is sent on verification - ConfigStore.setDisableAuth(true); - } } this.isAuthenticating = true; diff --git a/client/src/javascript/stores/ConfigStore.ts b/client/src/javascript/stores/ConfigStore.ts index fd88a8da..4814b8bf 100644 --- a/client/src/javascript/stores/ConfigStore.ts +++ b/client/src/javascript/stores/ConfigStore.ts @@ -1,5 +1,7 @@ import {makeAutoObservable} from 'mobx'; +import {AuthVerificationPreloadConfigs} from '@shared/schema/api/auth'; + class ConfigStore { baseURI = window.location.pathname.substr(0, window.location.pathname.lastIndexOf('/') + 1); disableAuth = false; @@ -9,8 +11,9 @@ class ConfigStore { makeAutoObservable(this); } - setDisableAuth(val: boolean): void { - this.disableAuth = val; + handlePreloadConfigs({disableAuth, pollInterval}: AuthVerificationPreloadConfigs) { + this.disableAuth = disableAuth != null ? disableAuth : false; + this.pollInterval = pollInterval || 2000; } } diff --git a/server/routes/api/auth.test.ts b/server/routes/api/auth.test.ts index 35cc5a55..b5fee84b 100644 --- a/server/routes/api/auth.test.ts +++ b/server/routes/api/auth.test.ts @@ -52,6 +52,7 @@ describe('GET /api/auth/verify (initial)', () => { const verificationResponse: AuthVerificationResponse = res.body; expect(verificationResponse.initialUser).toBe(true); + expect(verificationResponse.configs).toBeDefined(); done(); }); @@ -201,8 +202,11 @@ describe('GET /api/auth/verify', () => { .send() .set('Accept', 'application/json') .expect(401) - .end((err, _res) => { + .end((err, res) => { if (err) done(err); + + expect(res.body.configs).toBeDefined(); + done(); }); }); @@ -226,6 +230,8 @@ describe('GET /api/auth/verify', () => { expect(verificationResponse.username).toBe(testAdminUser.username); } + expect(verificationResponse.configs).toBeDefined(); + done(); }); }); @@ -237,9 +243,11 @@ describe('GET /api/auth/verify', () => { .set('Accept', 'application/json') .set('Cookie', [`jwt=${getAuthToken('nonExistentUser')}`]) .expect(401) - .end((err, _res) => { + .end((err, res) => { if (err) done(err); + expect(res.body.configs).toBeDefined(); + done(); }); }); diff --git a/server/routes/api/auth.ts b/server/routes/api/auth.ts index ea316e2c..ee99eee9 100644 --- a/server/routes/api/auth.ts +++ b/server/routes/api/auth.ts @@ -6,7 +6,12 @@ import rateLimit from 'express-rate-limit'; import type {Response} from 'express'; import ajaxUtil from '../../util/ajaxUtil'; -import {authAuthenticationSchema, authRegistrationSchema, authUpdateUserSchema} from '../../../shared/schema/api/auth'; +import { + authAuthenticationSchema, + authRegistrationSchema, + authUpdateUserSchema, + AuthVerificationPreloadConfigs, +} from '../../../shared/schema/api/auth'; import config from '../../../config'; import requireAdmin from '../../middleware/requireAdmin'; import services from '../../services'; @@ -75,6 +80,11 @@ const validationError = (res: Response, err: Error) => { }); }; +const preloadConfigs: AuthVerificationPreloadConfigs = { + disableAuth: config.disableUsersAndAuth, + pollInterval: config.torrentClientPollInterval, +}; + router.use('/users', passport.authenticate('jwt', {session: false}), requireAdmin); /** @@ -199,7 +209,7 @@ router.use('/verify', (req, res, next) => { initialUser: false, username, level, - token: `JWT ${token}`, + configs: preloadConfigs, }; res.json(response); @@ -210,11 +220,22 @@ router.use('/verify', (req, res, next) => { handleInitialUser: () => { const response: AuthVerificationResponse = { initialUser: true, + configs: preloadConfigs, }; res.json(response); }, handleSubsequentUser: () => { - passport.authenticate('jwt', {session: false})(req, res, next); + passport.authenticate('jwt', {session: false}, (err, user: UserInDatabase) => { + if (err || !user) { + res.status(401).json({ + configs: preloadConfigs, + }); + return; + } + + req.user = user; + next(); + })(req, res, next); }, }); }); @@ -238,6 +259,7 @@ router.get('/verify', (req, res) => { initialUser: false, username: req.user.username, level: req.user.level, + configs: preloadConfigs, }; res.json(response); diff --git a/shared/schema/api/auth.ts b/shared/schema/api/auth.ts index cbdf1171..f90fa4af 100644 --- a/shared/schema/api/auth.ts +++ b/shared/schema/api/auth.ts @@ -23,8 +23,14 @@ export type AuthRegistrationOptions = Required; +// GET /api/auth/verify - preload configurations +export interface AuthVerificationPreloadConfigs { + disableAuth: boolean; + pollInterval: number; +} + // GET /api/auth/verify - success response -export type AuthVerificationResponse = +export type AuthVerificationResponse = ( | { initialUser: true; } @@ -32,5 +38,7 @@ export type AuthVerificationResponse = initialUser: false; username: string; level: AccessLevel; - token?: string; - }; + } +) & { + configs: AuthVerificationPreloadConfigs; +};