mirror of
https://github.com/hay-kot/mealie.git
synced 2025-07-06 04:52:25 -07:00
* initial oidc implementation * add dynamic scheme * e2e test setup * add caching * fix * try this * add libldap-2.5 to runtime dependencies (#2849) * New translations en-us.json (Norwegian) (#2851) * New Crowdin updates (#2855) * New translations en-us.json (Italian) * New translations en-us.json (Norwegian) * New translations en-us.json (Portuguese) * fix * remove cache * cache yarn deps * cache docker image * cleanup action * lint * fix tests * remove not needed variables * run code gen * fix tests * add docs * move code into custom scheme * remove unneeded type * fix oidc admin * add more tests * add better spacing on login page * create auth providers * clean up testing stuff * type fixes * add OIDC auth method to postgres enum * add option to bypass login screen and go directly to iDP * remove check so we can fallback to another auth method oauth fails * Add provider name to be shown at the login screen * add new properties to admin about api * fix spec * add a prompt to change auth method when changing password * Create new auth section. Add more info on auth methods * update docs * run ruff * update docs * format * docs gen * formatting * initialize logger in class * mypy type fixes * docs gen * add models to get proper fields in docs and fix serialization * validate id token before using it * only request a mealie token on initial callback * remove unused method * fix unit tests * docs gen * check for valid idToken before getting token * add iss to mealie token * check to see if we already have a mealie token before getting one * fix lock file * update authlib * update lock file * add remember me environment variable * add user group setting to allow only certain groups to log in --------- Co-authored-by: Carter Mintey <cmintey8@gmail.com> Co-authored-by: Carter <35710697+cmintey@users.noreply.github.com>
57 lines
2.3 KiB
Python
57 lines
2.3 KiB
Python
from datetime import timedelta
|
|
|
|
from sqlalchemy.orm.session import Session
|
|
|
|
from mealie.core import root_logger
|
|
from mealie.core.config import get_app_settings
|
|
from mealie.core.exceptions import UserLockedOut
|
|
from mealie.core.security.hasher import get_hasher
|
|
from mealie.core.security.providers.auth_provider import AuthProvider
|
|
from mealie.repos.all_repositories import get_repositories
|
|
from mealie.schema.user.auth import CredentialsRequest
|
|
from mealie.services.user_services.user_service import UserService
|
|
|
|
|
|
class CredentialsProvider(AuthProvider[CredentialsRequest]):
|
|
"""Authentication provider that authenticates a user the database using username/password combination"""
|
|
|
|
_logger = root_logger.get_logger("credentials_provider")
|
|
|
|
def __init__(self, session: Session, data: CredentialsRequest) -> None:
|
|
super().__init__(session, data)
|
|
|
|
async def authenticate(self) -> tuple[str, timedelta] | None:
|
|
"""Attempt to authenticate a user given a username and password"""
|
|
settings = get_app_settings()
|
|
db = get_repositories(self.session)
|
|
user = self.try_get_user(self.data.username)
|
|
|
|
if not user:
|
|
# To prevent user enumeration we perform the verify_password computation to ensure
|
|
# server side time is relatively constant and not vulnerable to timing attacks.
|
|
CredentialsProvider.verify_password(
|
|
"abc123cba321", "$2b$12$JdHtJOlkPFwyxdjdygEzPOtYmdQF5/R5tHxw5Tq8pxjubyLqdIX5i"
|
|
)
|
|
return None
|
|
|
|
if user.login_attemps >= settings.SECURITY_MAX_LOGIN_ATTEMPTS or user.is_locked:
|
|
raise UserLockedOut()
|
|
|
|
if not CredentialsProvider.verify_password(self.data.password, user.password):
|
|
user.login_attemps += 1
|
|
db.users.update(user.id, user)
|
|
|
|
if user.login_attemps >= settings.SECURITY_MAX_LOGIN_ATTEMPTS:
|
|
user_service = UserService(db)
|
|
user_service.lock_user(user)
|
|
|
|
return None
|
|
|
|
user.login_attemps = 0
|
|
user = db.users.update(user.id, user)
|
|
return self.get_access_token(user, self.data.remember_me) # type: ignore
|
|
|
|
@staticmethod
|
|
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
|
"""Compares a plain string to a hashed password"""
|
|
return get_hasher().verify(plain_password, hashed_password)
|