Coverage for seedboxsync/core/database/models/user.py: 100%
30 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-31 17:10 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-31 17:10 +0000
1#
2# Copyright (C) 2015-2026 Guillaume Kulakowski <guillaume@kulakowski.fr>
3#
4# For the full copyright and license information, please view the LICENSE
5# file that was distributed with this source code.
6#
7"""Peewee DAO model for User."""
9import datetime
10from typing import Self
11from flask_login import UserMixin
12from peewee import AutoField, BooleanField, CharField, DateTimeField
13from werkzeug.security import check_password_hash
14from seedboxsync.core.database.models import SeedboxSyncModel
17class User(SeedboxSyncModel, UserMixin): # type: ignore[misc]
18 """
19 Data Access Object (DAO) representing a user.
21 This model stores user account information, including authentication
22 credentials, email address, account creation date, and last login timestamp.
24 Attributes:
25 id (int): Auto-incremented primary key.
26 username (str): Unique username of the user.
27 password (str): Hashed password of the user.
28 email (str): Unique email address of the user.
29 is_admin (bool): Indicates if the user has administrator privileges.
30 created (datetime): Timestamp when the user account was created.
31 last_login (datetime): Timestamp when the user last logged in.
32 """
34 ORIGIN_LOCAL = "local"
35 ORIGIN_OIDC = "oidc"
36 ORIGIN_CHOICES = (
37 (ORIGIN_LOCAL, "Local"),
38 (ORIGIN_OIDC, "OIDC"),
39 )
41 id = AutoField(help_text="Unique identifier of the user")
42 username = CharField(unique=True, help_text="Username of the user")
43 password = CharField(help_text="Salted password of the user")
44 origin = CharField(choices=ORIGIN_CHOICES, default=ORIGIN_LOCAL, max_length=10, help_text="Origin of the user account (local or oidc)")
45 email = CharField(unique=True, help_text="Email address of the user")
46 is_admin = BooleanField(default=False, help_text="Indicates if the user has administrator privileges")
47 created = DateTimeField(default=datetime.datetime.now, help_text="Timestamp when the user was created")
48 last_login = DateTimeField(null=True, help_text="Timestamp when the user last logged in")
50 @classmethod
51 def authenticate(cls, login: str, password: str) -> Self | None:
52 """
53 Authenticate a user using their username or email address.
55 Args:
56 login (str): Username or email address.
57 password (str): Plain-text password to verify.
59 Returns:
60 Self | None: The authenticated user, or None if authentication fails.
61 """
62 user = cls.get_or_none((cls.username == login) | (cls.email == login))
64 if user is None:
65 return None
67 if not check_password_hash(user.password, password):
68 return None
70 # Update last login timestamp
71 user.update_last_login()
73 return user
75 def update_last_login(self) -> None:
76 """
77 Update the last login timestamp for the user.
79 This method sets the `last_login` field to the current datetime and
80 saves the change to the database.
81 """
82 self.last_login = datetime.datetime.now()
83 self.save()