fixup!
This commit is contained in:
+3
-62
@@ -29,8 +29,6 @@ from .models import (
|
||||
PaymentFilters,
|
||||
PaymentHistoryPoint,
|
||||
TinyURL,
|
||||
UpdateUserPassword,
|
||||
UpdateUserPubkey,
|
||||
User,
|
||||
Wallet,
|
||||
WebPushSubscription,
|
||||
@@ -48,9 +46,10 @@ async def create_account(
|
||||
return account
|
||||
|
||||
|
||||
async def update_account(account: Account) -> None:
|
||||
async def update_account(account: Account) -> Account:
|
||||
account.updated_at = datetime.now()
|
||||
await db.update("accounts", account)
|
||||
return account
|
||||
|
||||
|
||||
async def delete_account(user_id: str, conn: Optional[Connection] = None) -> None:
|
||||
@@ -122,65 +121,6 @@ async def delete_accounts_no_wallets(
|
||||
)
|
||||
|
||||
|
||||
async def update_user_password(data: UpdateUserPassword, last_login_time: int) -> User:
|
||||
|
||||
assert 0 <= time() - last_login_time <= settings.auth_credetials_update_threshold, (
|
||||
"You can only update your credentials in the first"
|
||||
f" {settings.auth_credetials_update_threshold} seconds."
|
||||
" Please login again or ask a new reset key!"
|
||||
)
|
||||
assert data.password == data.password_repeat, "Passwords do not match."
|
||||
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
|
||||
await db.execute(
|
||||
f"""
|
||||
UPDATE accounts
|
||||
SET pass = :pass, updated_at = {db.timestamp_placeholder("now")}
|
||||
WHERE id = :user
|
||||
""",
|
||||
{
|
||||
"pass": pwd_context.hash(data.password),
|
||||
"now": int(time()),
|
||||
"user": data.user_id,
|
||||
},
|
||||
)
|
||||
|
||||
user = await get_user(data.user_id)
|
||||
assert user, "Updated account couldn't be retrieved."
|
||||
return user
|
||||
|
||||
|
||||
async def update_user_pubkey(data: UpdateUserPubkey, last_login_time: int) -> User:
|
||||
|
||||
assert 0 <= time() - last_login_time <= settings.auth_credetials_update_threshold, (
|
||||
"You can only update your credentials in the first"
|
||||
f" {settings.auth_credetials_update_threshold} seconds after login."
|
||||
" Please login again!"
|
||||
)
|
||||
|
||||
user = await get_account_by_pubkey(data.pubkey)
|
||||
if user:
|
||||
assert user.id == data.user_id, "Public key already in use."
|
||||
|
||||
await db.execute(
|
||||
f"""
|
||||
UPDATE accounts
|
||||
SET pubkey = :pubkey, updated_at = {db.timestamp_placeholder("now")}
|
||||
WHERE id = :user
|
||||
""",
|
||||
{
|
||||
"pubkey": data.pubkey,
|
||||
"now": int(time()),
|
||||
"user": data.user_id,
|
||||
},
|
||||
)
|
||||
|
||||
user = await get_user(data.user_id)
|
||||
assert user, "Updated account couldn't be retrieved"
|
||||
return user
|
||||
|
||||
|
||||
async def get_account_by_username(
|
||||
username: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Account]:
|
||||
@@ -200,6 +140,7 @@ async def get_account_by_pubkey(
|
||||
Account,
|
||||
)
|
||||
|
||||
|
||||
async def get_account_by_email(
|
||||
email: str, conn: Optional[Connection] = None
|
||||
) -> Optional[Account]:
|
||||
|
||||
@@ -112,6 +112,7 @@ class Account(BaseModel):
|
||||
id: str
|
||||
username: Optional[str] = None
|
||||
password_hash: Optional[str] = None
|
||||
pubkey: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
extra: UserExtra = UserExtra()
|
||||
created_at: datetime = datetime.now()
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import base64
|
||||
import importlib
|
||||
import json
|
||||
from time import time
|
||||
from http import HTTPStatus
|
||||
from time import time
|
||||
from typing import Callable, Optional
|
||||
from uuid import uuid4
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from fastapi.responses import JSONResponse, RedirectResponse
|
||||
from fastapi_sso.sso.base import OpenID, SSOBase
|
||||
from loguru import logger
|
||||
|
||||
from lnbits.core.services import create_user_account
|
||||
from lnbits.decorators import access_token_payload, check_user_exists
|
||||
from lnbits.helpers import (
|
||||
create_access_token,
|
||||
@@ -31,13 +31,8 @@ from ..crud import (
|
||||
get_account_by_username,
|
||||
get_account_by_username_or_email,
|
||||
get_user,
|
||||
get_user_password,
|
||||
update_account,
|
||||
update_user_password,
|
||||
update_user_pubkey,
|
||||
verify_user_password,
|
||||
)
|
||||
|
||||
from ..models import (
|
||||
AccessTokenPayload,
|
||||
Account,
|
||||
@@ -78,25 +73,29 @@ async def login(data: LoginUsernamePassword) -> JSONResponse:
|
||||
@auth_router.post("/nostr", description="Login via Nostr")
|
||||
async def nostr_login(request: Request) -> JSONResponse:
|
||||
if not settings.is_auth_method_allowed(AuthMethods.nostr_auth_nip98):
|
||||
raise HTTPException(HTTP_401_UNAUTHORIZED, "Login with Nostr Auth not allowed.")
|
||||
raise HTTPException(
|
||||
HTTPStatus.UNAUTHORIZED, "Login with Nostr Auth not allowed."
|
||||
)
|
||||
|
||||
try:
|
||||
event = _nostr_nip98_event(request)
|
||||
|
||||
user = await get_account_by_pubkey(event["pubkey"])
|
||||
if not user:
|
||||
user = await create_user_account(
|
||||
pubkey=event["pubkey"], user_config=UserConfig(provider="nostr")
|
||||
account = await get_account_by_pubkey(event["pubkey"])
|
||||
if not account:
|
||||
account = Account(
|
||||
id=uuid4().hex,
|
||||
pubkey=event["pubkey"],
|
||||
extra=UserExtra(provider="nostr"),
|
||||
)
|
||||
await create_account(account)
|
||||
|
||||
return _auth_success_response(user.username or "", user.id, user.email)
|
||||
return _auth_success_response(account.username or "", account.id, account.email)
|
||||
except HTTPException as exc:
|
||||
raise exc
|
||||
except AssertionError as exc:
|
||||
raise HTTPException(HTTP_401_UNAUTHORIZED, str(exc)) from exc
|
||||
raise HTTPException(HTTPStatus.UNAUTHORIZED, str(exc)) from exc
|
||||
except Exception as exc:
|
||||
logger.warning(exc)
|
||||
raise HTTPException(HTTP_500_INTERNAL_SERVER_ERROR, "Cannot login.") from exc
|
||||
raise HTTPException(HTTPStatus.INTERNAL_SERVER_ERROR, "Cannot login.") from exc
|
||||
|
||||
|
||||
@auth_router.post("/usr", description="Login via the User ID")
|
||||
@@ -205,7 +204,6 @@ async def register(data: CreateUser) -> JSONResponse:
|
||||
return _auth_success_response(account.username)
|
||||
|
||||
|
||||
|
||||
@auth_router.put("/pubkey")
|
||||
async def update_pubkey(
|
||||
data: UpdateUserPubkey,
|
||||
@@ -213,16 +211,22 @@ async def update_pubkey(
|
||||
payload: AccessTokenPayload = Depends(access_token_payload),
|
||||
) -> Optional[User]:
|
||||
if data.user_id != user.id:
|
||||
raise HTTPException(HTTP_400_BAD_REQUEST, "Invalid user ID.")
|
||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Invalid user ID.")
|
||||
|
||||
try:
|
||||
data.pubkey = normalize_public_key(data.pubkey)
|
||||
return await update_user_pubkey(data, payload.auth_time or 0)
|
||||
|
||||
except AssertionError as exc:
|
||||
raise HTTPException(HTTP_403_FORBIDDEN, str(exc)) from exc
|
||||
except Exception as exc:
|
||||
logger.debug(exc)
|
||||
account = await get_account(user.id)
|
||||
if not account:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.NOT_FOUND, detail="Account not found."
|
||||
)
|
||||
account_existing = await get_account_by_pubkey(data.pubkey)
|
||||
if account_existing and account_existing.id != account.id:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST, detail="Public key already in use."
|
||||
)
|
||||
_validate_auth_timeout(payload.auth_time)
|
||||
account.pubkey = normalize_public_key(data.pubkey)
|
||||
await update_account(account)
|
||||
return await get_user(account)
|
||||
|
||||
|
||||
@auth_router.put("/password")
|
||||
@@ -257,6 +261,8 @@ async def update_password(
|
||||
status_code=HTTPStatus.BAD_REQUEST, detail="Invalid credentials."
|
||||
)
|
||||
|
||||
_validate_auth_timeout(payload.auth_time)
|
||||
|
||||
account.username = data.username
|
||||
account.hash_password(data.password)
|
||||
await update_account(account)
|
||||
@@ -389,7 +395,7 @@ async def _handle_sso_login(userinfo: OpenID, verified_user_id: Optional[str] =
|
||||
if not settings.new_accounts_allowed:
|
||||
raise HTTPException(HTTPStatus.BAD_REQUEST, "Account creation is disabled.")
|
||||
account = Account(
|
||||
id=urlsafe_short_hash(), email=email, extra=UserExtra(email_verified=True)
|
||||
id=uuid4().hex, email=email, extra=UserExtra(email_verified=True)
|
||||
)
|
||||
await create_account(account)
|
||||
return _auth_redirect_response(redirect_path, email)
|
||||
@@ -499,3 +505,13 @@ def _nostr_nip98_event(request: Request) -> dict:
|
||||
assert url in accepted_urls, f"Incorrect value for tag 'u': '{url}'."
|
||||
|
||||
return event
|
||||
|
||||
|
||||
def _validate_auth_timeout(auth_time: Optional[int] = None):
|
||||
if int(time()) - int(auth_time or 0) > settings.auth_credetials_update_threshold:
|
||||
raise HTTPException(
|
||||
HTTPStatus.BAD_REQUEST,
|
||||
"You can only update your credentials in the first"
|
||||
f" {settings.auth_credetials_update_threshold} seconds after login."
|
||||
" Please login again!",
|
||||
)
|
||||
|
||||
+14
-8
@@ -9,6 +9,8 @@ from lnbits.wallets.fake import FakeWallet
|
||||
|
||||
uvloop.install()
|
||||
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
from fastapi.testclient import TestClient
|
||||
@@ -18,13 +20,13 @@ from lnbits.app import create_app
|
||||
from lnbits.core.crud import (
|
||||
create_account,
|
||||
create_wallet,
|
||||
get_account_by_username,
|
||||
get_account,
|
||||
get_account_by_username,
|
||||
get_user,
|
||||
update_payment_status,
|
||||
)
|
||||
from lnbits.core.models import CreateInvoice, PaymentState
|
||||
from lnbits.core.services import create_user_account, update_wallet_balance
|
||||
from lnbits.core.models import Account, CreateInvoice, PaymentState
|
||||
from lnbits.core.services import update_wallet_balance
|
||||
from lnbits.core.views.payment_api import api_payments_create_invoice
|
||||
from lnbits.db import DB_TYPE, SQLITE, Database
|
||||
from lnbits.settings import AuthMethods, settings
|
||||
@@ -102,12 +104,16 @@ async def db():
|
||||
|
||||
@pytest_asyncio.fixture(scope="package")
|
||||
async def user_alan():
|
||||
user = await get_account_by_username("alan")
|
||||
if not user:
|
||||
user = await create_user_account(
|
||||
email="alan@lnbits.com", username="alan", password="secret1234"
|
||||
account = await get_account_by_username("alan")
|
||||
if not account:
|
||||
account = Account(
|
||||
id=uuid4().hex,
|
||||
email="alan@lnbits.com",
|
||||
username="alan",
|
||||
)
|
||||
yield user
|
||||
account.hash_password("secret1234")
|
||||
await create_account(account)
|
||||
yield account
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
|
||||
Reference in New Issue
Block a user