[feat] configure HTTPS Only settings (#3801)
This commit is contained in:
@@ -22,6 +22,7 @@ jobs:
|
|||||||
- name: run LNbits
|
- name: run LNbits
|
||||||
env:
|
env:
|
||||||
LNBITS_ADMIN_UI: true
|
LNBITS_ADMIN_UI: true
|
||||||
|
AUTH_HTTPS_ONLY: false
|
||||||
LNBITS_EXTENSIONS_DEFAULT_INSTALL: "watchonly, satspay, tipjar, tpos, lnurlp, withdraw"
|
LNBITS_EXTENSIONS_DEFAULT_INSTALL: "watchonly, satspay, tipjar, tpos, lnurlp, withdraw"
|
||||||
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
|
LNBITS_BACKEND_WALLET_CLASS: FakeWallet
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -154,9 +154,20 @@ async def impersonate_user(
|
|||||||
|
|
||||||
max_age = settings.auth_token_expire_minutes * 60
|
max_age = settings.auth_token_expire_minutes * 60
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
"admin_access_token", cookie_access_token, httponly=True, max_age=max_age
|
"admin_access_token",
|
||||||
|
cookie_access_token,
|
||||||
|
httponly=True,
|
||||||
|
secure=settings.auth_https_only,
|
||||||
|
samesite="lax",
|
||||||
|
max_age=max_age,
|
||||||
|
)
|
||||||
|
response.set_cookie(
|
||||||
|
"is_lnbits_user_impersonated",
|
||||||
|
"true",
|
||||||
|
secure=settings.auth_https_only,
|
||||||
|
samesite="lax",
|
||||||
|
max_age=max_age,
|
||||||
)
|
)
|
||||||
response.set_cookie("is_lnbits_user_impersonated", "true", max_age=max_age)
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
||||||
@@ -177,7 +188,12 @@ async def stop_impersonate_user(
|
|||||||
)
|
)
|
||||||
max_age = settings.auth_token_expire_minutes * 60
|
max_age = settings.auth_token_expire_minutes * 60
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
"cookie_access_token", admin_access_token, httponly=True, max_age=max_age
|
"cookie_access_token",
|
||||||
|
admin_access_token,
|
||||||
|
httponly=True,
|
||||||
|
secure=settings.auth_https_only,
|
||||||
|
samesite="lax",
|
||||||
|
max_age=max_age,
|
||||||
)
|
)
|
||||||
response.delete_cookie("admin_access_token")
|
response.delete_cookie("admin_access_token")
|
||||||
response.delete_cookie("is_access_token_expired")
|
response.delete_cookie("is_access_token_expired")
|
||||||
@@ -560,7 +576,7 @@ async def _handle_sso_login(userinfo: OpenID, verified_user_id: str | None = Non
|
|||||||
id=uuid4().hex, email=email, extra=UserExtra(email_verified=True)
|
id=uuid4().hex, email=email, extra=UserExtra(email_verified=True)
|
||||||
)
|
)
|
||||||
await create_user_account(account)
|
await create_user_account(account)
|
||||||
return _auth_redirect_response(redirect_path, email)
|
return _auth_redirect_response(redirect_path, account.id, email)
|
||||||
|
|
||||||
|
|
||||||
def _auth_success_response(
|
def _auth_success_response(
|
||||||
@@ -575,9 +591,20 @@ def _auth_success_response(
|
|||||||
max_age = settings.auth_token_expire_minutes * 60
|
max_age = settings.auth_token_expire_minutes * 60
|
||||||
response = JSONResponse({"access_token": access_token, "token_type": "bearer"})
|
response = JSONResponse({"access_token": access_token, "token_type": "bearer"})
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
"cookie_access_token", access_token, httponly=True, max_age=max_age
|
"cookie_access_token",
|
||||||
|
access_token,
|
||||||
|
httponly=True,
|
||||||
|
secure=settings.auth_https_only,
|
||||||
|
samesite="lax",
|
||||||
|
max_age=max_age,
|
||||||
|
)
|
||||||
|
response.set_cookie(
|
||||||
|
"is_lnbits_user_authorized",
|
||||||
|
"true",
|
||||||
|
secure=settings.auth_https_only,
|
||||||
|
samesite="lax",
|
||||||
|
max_age=max_age,
|
||||||
)
|
)
|
||||||
response.set_cookie("is_lnbits_user_authorized", "true", max_age=max_age)
|
|
||||||
response.delete_cookie("is_access_token_expired")
|
response.delete_cookie("is_access_token_expired")
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -594,15 +621,28 @@ def _auth_api_token_response(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _auth_redirect_response(path: str, email: str) -> RedirectResponse:
|
def _auth_redirect_response(path: str, user_id: str, email: str) -> RedirectResponse:
|
||||||
payload = AccessTokenPayload(sub="" or "", email=email, auth_time=int(time()))
|
payload = AccessTokenPayload(
|
||||||
|
usr=user_id, sub="", email=email, auth_time=int(time())
|
||||||
|
)
|
||||||
access_token = create_access_token(data=payload.dict())
|
access_token = create_access_token(data=payload.dict())
|
||||||
max_age = settings.auth_token_expire_minutes * 60
|
max_age = settings.auth_token_expire_minutes * 60
|
||||||
response = RedirectResponse(path)
|
response = RedirectResponse(path)
|
||||||
response.set_cookie(
|
response.set_cookie(
|
||||||
"cookie_access_token", access_token, httponly=True, max_age=max_age
|
"cookie_access_token",
|
||||||
|
access_token,
|
||||||
|
httponly=True,
|
||||||
|
secure=settings.auth_https_only,
|
||||||
|
samesite="lax",
|
||||||
|
max_age=max_age,
|
||||||
|
)
|
||||||
|
response.set_cookie(
|
||||||
|
"is_lnbits_user_authorized",
|
||||||
|
"true",
|
||||||
|
secure=settings.auth_https_only,
|
||||||
|
samesite="lax",
|
||||||
|
max_age=max_age,
|
||||||
)
|
)
|
||||||
response.set_cookie("is_lnbits_user_authorized", "true", max_age=max_age)
|
|
||||||
response.delete_cookie("is_access_token_expired")
|
response.delete_cookie("is_access_token_expired")
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@@ -622,7 +662,10 @@ def _new_sso(provider: str) -> SSOBase | None:
|
|||||||
|
|
||||||
sso_provider_class = _find_auth_provider_class(provider)
|
sso_provider_class = _find_auth_provider_class(provider)
|
||||||
sso_provider = sso_provider_class(
|
sso_provider = sso_provider_class(
|
||||||
client_id, client_secret, None, allow_insecure_http=True
|
client_id,
|
||||||
|
client_secret,
|
||||||
|
None,
|
||||||
|
allow_insecure_http=not settings.auth_https_only,
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
discovery_url
|
discovery_url
|
||||||
|
|||||||
@@ -1005,6 +1005,9 @@ class EnvSettings(LNbitsSettings):
|
|||||||
debug: bool = Field(default=False)
|
debug: bool = Field(default=False)
|
||||||
debug_database: bool = Field(default=False)
|
debug_database: bool = Field(default=False)
|
||||||
bundle_assets: bool = Field(default=True)
|
bundle_assets: bool = Field(default=True)
|
||||||
|
# When enabled, auth cookies require HTTPS and SSO will reject insecure HTTP.
|
||||||
|
# Set to false for local/dev environments that run without TLS.
|
||||||
|
auth_https_only: bool = Field(default=True)
|
||||||
host: str = Field(default="127.0.0.1")
|
host: str = Field(default="127.0.0.1")
|
||||||
port: int = Field(default=5000, gt=0)
|
port: int = Field(default=5000, gt=0)
|
||||||
forwarded_allow_ips: str = Field(default="*")
|
forwarded_allow_ips: str = Field(default="*")
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ def anyio_backend():
|
|||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def settings():
|
def settings():
|
||||||
# override settings for tests
|
# override settings for tests
|
||||||
|
lnbits_settings.auth_https_only = False
|
||||||
lnbits_settings.lnbits_admin_extensions = []
|
lnbits_settings.lnbits_admin_extensions = []
|
||||||
lnbits_settings.lnbits_data_folder = "./tests/data"
|
lnbits_settings.lnbits_data_folder = "./tests/data"
|
||||||
lnbits_settings.lnbits_admin_ui = True
|
lnbits_settings.lnbits_admin_ui = True
|
||||||
|
|||||||
Reference in New Issue
Block a user