diff --git a/client/pages/login.vue b/client/pages/login.vue index 3f48509f..71fa8c2b 100644 --- a/client/pages/login.vue +++ b/client/pages/login.vue @@ -40,6 +40,15 @@

{{ error }}

+
+ +
+

Authentication has been improved for security. All users will be required to re-login.

+ More info +
+
+
+
@@ -85,7 +94,8 @@ export default { MetadataPath: '', login_local: true, login_openid: false, - authFormData: null + authFormData: null, + showNewAuthSystemAdminMessage: false } }, watch: { @@ -184,6 +194,7 @@ export default { }, async submitForm() { this.error = null + this.showNewAuthSystemAdminMessage = false this.processing = true const payload = { @@ -217,15 +228,28 @@ export default { } }) .then((res) => { + // Force re-login if user is using an old token with no expiration + if (res.user.isOldToken) { + if (res.user.type === 'admin' || res.user.type === 'root') { + this.username = res.user.username + // Show message to admin users about new auth system + this.showNewAuthSystemAdminMessage = true + } else { + // Regular users just shown login + this.username = res.user.username + } + return false + } this.setUser(res) - this.processing = false return true }) .catch((error) => { console.error('Authorize error', error) - this.processing = false return false }) + .finally(() => { + this.processing = false + }) }, checkStatus() { this.processing = true diff --git a/server/Auth.js b/server/Auth.js index 1b3ba601..d2250f17 100644 --- a/server/Auth.js +++ b/server/Auth.js @@ -492,6 +492,7 @@ class Auth { } if (!refreshToken) { + Logger.error(`[Auth] Failed to refresh token. No refresh token provided`) return res.status(401).json({ error: 'No refresh token provided' }) } @@ -502,6 +503,7 @@ class Auth { const decoded = jwt.verify(refreshToken, global.ServerSettings.tokenSecret) if (decoded.type !== 'refresh') { + Logger.error(`[Auth] Failed to refresh token. Invalid token type: ${decoded.type}`) return res.status(401).json({ error: 'Invalid token type' }) } @@ -510,6 +512,7 @@ class Auth { }) if (!session) { + Logger.error(`[Auth] Failed to refresh token. Session not found for refresh token: ${refreshToken}`) return res.status(401).json({ error: 'Invalid refresh token' }) } @@ -522,6 +525,7 @@ class Auth { const user = await Database.userModel.getUserById(decoded.userId) if (!user?.isActive) { + Logger.error(`[Auth] Failed to refresh token. User not found or inactive for user id: ${decoded.userId}`) return res.status(401).json({ error: 'User not found or inactive' }) } @@ -1128,6 +1132,16 @@ class Auth { done(null, null) return } + + // TODO: Temporary flag to report old tokens to users + // May be a better place for this but here means we dont have to decode the token again + if (!jwt_payload.exp && !user.isOldToken) { + Logger.debug(`[Auth] User ${user.username} is using an access token without an expiration`) + user.isOldToken = true + } else if (jwt_payload.exp && user.isOldToken !== undefined) { + delete user.isOldToken + } + // approve login done(null, user) } diff --git a/server/models/User.js b/server/models/User.js index 588b53bb..154587a7 100644 --- a/server/models/User.js +++ b/server/models/User.js @@ -522,6 +522,9 @@ class User extends Model { type: this.type, // TODO: Old non-expiring token token: this.type === 'root' && hideRootToken ? '' : this.token, + // TODO: Temporary flag not saved in db that is set in Auth.js jwtAuthCheck + // Necessary to detect apps using old tokens that no longer match the old token stored on the user + isOldToken: this.isOldToken, mediaProgress: this.mediaProgresses?.map((mp) => mp.getOldMediaProgress()) || [], seriesHideFromContinueListening: [...seriesHideFromContinueListening], bookmarks: this.bookmarks?.map((b) => ({ ...b })) || [],