diff --git a/gns3server/db/models/roles.py b/gns3server/db/models/roles.py index 2cc54f37..6cba0cc1 100644 --- a/gns3server/db/models/roles.py +++ b/gns3server/db/models/roles.py @@ -38,7 +38,7 @@ class Role(BaseTable): __tablename__ = "roles" role_id = Column(GUID, primary_key=True, default=generate_uuid) - name = Column(String) + name = Column(String, unique=True) description = Column(String) is_builtin = Column(Boolean, default=False) permissions = relationship("Permission", secondary=permission_role_link, back_populates="roles") diff --git a/gns3server/db/models/users.py b/gns3server/db/models/users.py index 8cf3d503..e6bc53f4 100644 --- a/gns3server/db/models/users.py +++ b/gns3server/db/models/users.py @@ -15,7 +15,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -from sqlalchemy import Table, Boolean, Column, String, ForeignKey, event +from sqlalchemy import Table, Boolean, Column, String, DateTime, ForeignKey, event from sqlalchemy.orm import relationship from .base import Base, BaseTable, generate_uuid, GUID @@ -45,6 +45,7 @@ class User(BaseTable): email = Column(String, unique=True, index=True) full_name = Column(String) hashed_password = Column(String) + last_login = Column(DateTime) is_active = Column(Boolean, default=True) is_superadmin = Column(Boolean, default=False) groups = relationship("UserGroup", secondary=user_group_link, back_populates="users") diff --git a/gns3server/db/repositories/users.py b/gns3server/db/repositories/users.py index 68d12f78..2db1516f 100644 --- a/gns3server/db/repositories/users.py +++ b/gns3server/db/repositories/users.py @@ -17,7 +17,7 @@ from uuid import UUID from typing import Optional, List, Union -from sqlalchemy import select, update, delete +from sqlalchemy import select, update, delete, func from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload @@ -140,6 +140,15 @@ class UsersRepository(BaseRepository): return user if not self._auth_service.verify_password(password, user.hashed_password): return None + + # Backup the updated_at value + updated_at = user.updated_at + user.last_login = func.current_timestamp() + await self._db_session.commit() + # Restore the original updated_at value + # so it is not affected by the last login update + user.updated_at = updated_at + await self._db_session.commit() return user async def get_user_memberships(self, user_id: UUID) -> List[models.UserGroup]: diff --git a/gns3server/schemas/controller/users.py b/gns3server/schemas/controller/users.py index fc60568c..89effbb3 100644 --- a/gns3server/schemas/controller/users.py +++ b/gns3server/schemas/controller/users.py @@ -14,6 +14,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . +from datetime import datetime from typing import Optional from pydantic import EmailStr, BaseModel, Field, SecretStr from uuid import UUID @@ -51,6 +52,7 @@ class UserUpdate(UserBase): class User(DateTimeModelMixin, UserBase): user_id: UUID + last_login: Optional[datetime] = None is_active: bool = True is_superadmin: bool = False