mirror of
https://github.com/mealie-recipes/mealie.git
synced 2025-10-27 16:24:31 -04:00
fix: Actually Fix Token Time (#6215)
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { defineNuxtConfig } from "nuxt/config";
|
import { defineNuxtConfig } from "nuxt/config";
|
||||||
|
|
||||||
const AUTH_TOKEN = "mealie.auth.token";
|
const AUTH_TOKEN = "mealie.access_token";
|
||||||
|
|
||||||
export default defineNuxtConfig({
|
export default defineNuxtConfig({
|
||||||
// Global page headers: https://go.nuxtjs.dev/config-head
|
// Global page headers: https://go.nuxtjs.dev/config-head
|
||||||
@@ -142,7 +142,6 @@ export default defineNuxtConfig({
|
|||||||
signInResponseTokenPointer: "/access_token",
|
signInResponseTokenPointer: "/access_token",
|
||||||
type: "Bearer",
|
type: "Bearer",
|
||||||
cookieName: AUTH_TOKEN,
|
cookieName: AUTH_TOKEN,
|
||||||
maxAgeInSeconds: parseInt(process.env.TOKEN_TIME || "48") * 3600, // TOKEN_TIME is in hours
|
|
||||||
},
|
},
|
||||||
pages: {
|
pages: {
|
||||||
login: "/login",
|
login: "/login",
|
||||||
|
|||||||
@@ -30,7 +30,23 @@ export default defineNuxtPlugin(() => {
|
|||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error?.response?.data?.detail?.message) alert.error(error.response.data.detail.message as string);
|
if (error?.response?.data?.detail?.message) {
|
||||||
|
alert.error(error.response.data.detail.message as string);
|
||||||
|
};
|
||||||
|
|
||||||
|
// If we receive a 401 Unauthorized response, clear the token cookie and redirect to login
|
||||||
|
if (error?.response?.status === 401) {
|
||||||
|
// If tokenCookie is not set, we may just be an unauthenticated user using the wrong API, so don't redirect
|
||||||
|
const tokenCookie = useCookie(tokenName);
|
||||||
|
if (tokenCookie.value) {
|
||||||
|
tokenCookie.value = null;
|
||||||
|
|
||||||
|
// Disable beforeunload warnings to prevent "Are you sure you want to leave?" popups
|
||||||
|
window.onbeforeunload = null;
|
||||||
|
window.location.href = "/login";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -30,8 +30,8 @@ class AuthProvider[T](metaclass=abc.ABCMeta):
|
|||||||
settings = get_app_settings()
|
settings = get_app_settings()
|
||||||
|
|
||||||
duration = timedelta(hours=settings.TOKEN_TIME)
|
duration = timedelta(hours=settings.TOKEN_TIME)
|
||||||
if remember_me and remember_me_duration > duration:
|
if remember_me:
|
||||||
duration = remember_me_duration
|
duration = max(remember_me_duration, duration)
|
||||||
|
|
||||||
return AuthProvider.create_access_token({"sub": str(user.id)}, duration)
|
return AuthProvider.create_access_token({"sub": str(user.id)}, duration)
|
||||||
|
|
||||||
|
|||||||
@@ -123,6 +123,17 @@ class AppSettings(AppLoggingSettings):
|
|||||||
TOKEN_TIME: int = 48
|
TOKEN_TIME: int = 48
|
||||||
"""time in hours"""
|
"""time in hours"""
|
||||||
|
|
||||||
|
@field_validator("TOKEN_TIME")
|
||||||
|
@classmethod
|
||||||
|
def validate_token_time(cls, v: int) -> int:
|
||||||
|
if v < 1:
|
||||||
|
raise ValueError("TOKEN_TIME must be at least 1 hour")
|
||||||
|
# If TOKEN_TIME is unreasonably high (e.g. hundreds of years), JWT encoding
|
||||||
|
# can overflow, so we set the max to 10 years (87600 hours).
|
||||||
|
if v > 87600:
|
||||||
|
raise ValueError("TOKEN_TIME is too high; maximum is 87600 hours (10 years)")
|
||||||
|
return v
|
||||||
|
|
||||||
SECRET: str
|
SECRET: str
|
||||||
SESSION_SECRET: str
|
SESSION_SECRET: str
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from authlib.integrations.starlette_client import OAuth
|
from authlib.integrations.starlette_client import OAuth
|
||||||
from fastapi import APIRouter, Depends, Request, Response, status
|
from fastapi import APIRouter, Depends, Request, Response, status
|
||||||
from fastapi.exceptions import HTTPException
|
from fastapi.exceptions import HTTPException
|
||||||
@@ -25,7 +23,6 @@ public_router = APIRouter(tags=["Users: Authentication"])
|
|||||||
user_router = UserAPIRouter(tags=["Users: Authentication"])
|
user_router = UserAPIRouter(tags=["Users: Authentication"])
|
||||||
logger = root_logger.get_logger("auth")
|
logger = root_logger.get_logger("auth")
|
||||||
|
|
||||||
remember_me_duration = timedelta(days=14)
|
|
||||||
|
|
||||||
settings = get_app_settings()
|
settings = get_app_settings()
|
||||||
if settings.OIDC_READY:
|
if settings.OIDC_READY:
|
||||||
@@ -54,6 +51,19 @@ class MealieAuthToken(BaseModel):
|
|||||||
access_token: str
|
access_token: str
|
||||||
token_type: str = "bearer"
|
token_type: str = "bearer"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def set_cookie(cls, response: Response, token: str, expires_in: int | float | None = None):
|
||||||
|
expires_in = int(expires_in) if expires_in else None
|
||||||
|
|
||||||
|
# httponly=False to allow JS access for frontend
|
||||||
|
response.set_cookie(
|
||||||
|
key="mealie.access_token",
|
||||||
|
value=token,
|
||||||
|
httponly=False,
|
||||||
|
max_age=expires_in,
|
||||||
|
secure=settings.PRODUCTION,
|
||||||
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def respond(cls, token: str, token_type: str = "bearer") -> dict:
|
def respond(cls, token: str, token_type: str = "bearer") -> dict:
|
||||||
return cls(access_token=token, token_type=token_type).model_dump()
|
return cls(access_token=token, token_type=token_type).model_dump()
|
||||||
@@ -86,17 +96,11 @@ def get_token(
|
|||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||||
)
|
)
|
||||||
|
|
||||||
access_token, duration = auth
|
access_token, duration = auth
|
||||||
|
|
||||||
expires_in = duration.total_seconds() if duration else None
|
expires_in = duration.total_seconds() if duration else None
|
||||||
response.set_cookie(
|
|
||||||
key="mealie.access_token",
|
|
||||||
value=access_token,
|
|
||||||
httponly=True,
|
|
||||||
max_age=expires_in,
|
|
||||||
secure=settings.PRODUCTION,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
MealieAuthToken.set_cookie(response, access_token, expires_in)
|
||||||
return MealieAuthToken.respond(access_token)
|
return MealieAuthToken.respond(access_token)
|
||||||
|
|
||||||
|
|
||||||
@@ -145,18 +149,11 @@ async def oauth_callback(request: Request, response: Response, session: Session
|
|||||||
|
|
||||||
if not auth:
|
if not auth:
|
||||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||||
access_token, duration = auth
|
|
||||||
|
|
||||||
|
access_token, duration = auth
|
||||||
expires_in = duration.total_seconds() if duration else None
|
expires_in = duration.total_seconds() if duration else None
|
||||||
|
|
||||||
response.set_cookie(
|
MealieAuthToken.set_cookie(response, access_token, expires_in)
|
||||||
key="mealie.access_token",
|
|
||||||
value=access_token,
|
|
||||||
httponly=True,
|
|
||||||
max_age=expires_in,
|
|
||||||
secure=settings.PRODUCTION,
|
|
||||||
)
|
|
||||||
|
|
||||||
return MealieAuthToken.respond(access_token)
|
return MealieAuthToken.respond(access_token)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user