- Added RoleRepository and UserRepository for managing roles and users. - Implemented methods for creating, retrieving, and assigning roles to users. - Introduced functions to ensure default roles and an admin user exist in the system. - Updated UnitOfWork to include user and role repositories. - Created new security module for password hashing and JWT token management. - Added tests for authentication flows, including registration, login, and password reset. - Enhanced HTML templates for user registration, login, and password management with error handling. - Added a logo image to the static assets.
211 lines
5.9 KiB
Python
211 lines
5.9 KiB
Python
"""Add authentication and RBAC tables"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from passlib.context import CryptContext
|
|
from sqlalchemy.sql import column, table
|
|
|
|
# revision identifiers, used by Alembic.
|
|
revision = "20251109_02"
|
|
down_revision = "20251109_01"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
password_context = CryptContext(schemes=["argon2"], deprecated="auto")
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"users",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("email", sa.String(length=255), nullable=False),
|
|
sa.Column("username", sa.String(length=128), nullable=False),
|
|
sa.Column("password_hash", sa.String(length=255), nullable=False),
|
|
sa.Column(
|
|
"is_active",
|
|
sa.Boolean(),
|
|
nullable=False,
|
|
server_default=sa.true(),
|
|
),
|
|
sa.Column(
|
|
"is_superuser",
|
|
sa.Boolean(),
|
|
nullable=False,
|
|
server_default=sa.false(),
|
|
),
|
|
sa.Column("last_login_at", sa.DateTime(timezone=True), nullable=True),
|
|
sa.Column(
|
|
"created_at",
|
|
sa.DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.Column(
|
|
"updated_at",
|
|
sa.DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.UniqueConstraint("email", name="uq_users_email"),
|
|
sa.UniqueConstraint("username", name="uq_users_username"),
|
|
)
|
|
op.create_index(
|
|
"ix_users_active_superuser",
|
|
"users",
|
|
["is_active", "is_superuser"],
|
|
unique=False,
|
|
)
|
|
|
|
op.create_table(
|
|
"roles",
|
|
sa.Column("id", sa.Integer(), primary_key=True),
|
|
sa.Column("name", sa.String(length=64), nullable=False),
|
|
sa.Column("display_name", sa.String(length=128), nullable=False),
|
|
sa.Column("description", sa.Text(), nullable=True),
|
|
sa.Column(
|
|
"created_at",
|
|
sa.DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.Column(
|
|
"updated_at",
|
|
sa.DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.UniqueConstraint("name", name="uq_roles_name"),
|
|
)
|
|
|
|
op.create_table(
|
|
"user_roles",
|
|
sa.Column("user_id", sa.Integer(), nullable=False),
|
|
sa.Column("role_id", sa.Integer(), nullable=False),
|
|
sa.Column(
|
|
"granted_at",
|
|
sa.DateTime(timezone=True),
|
|
nullable=False,
|
|
server_default=sa.func.now(),
|
|
),
|
|
sa.Column("granted_by", sa.Integer(), nullable=True),
|
|
sa.ForeignKeyConstraint(
|
|
["user_id"],
|
|
["users.id"],
|
|
ondelete="CASCADE",
|
|
),
|
|
sa.ForeignKeyConstraint(
|
|
["role_id"],
|
|
["roles.id"],
|
|
ondelete="CASCADE",
|
|
),
|
|
sa.ForeignKeyConstraint(
|
|
["granted_by"],
|
|
["users.id"],
|
|
ondelete="SET NULL",
|
|
),
|
|
sa.PrimaryKeyConstraint("user_id", "role_id"),
|
|
sa.UniqueConstraint("user_id", "role_id",
|
|
name="uq_user_roles_user_role"),
|
|
)
|
|
op.create_index(
|
|
"ix_user_roles_role_id",
|
|
"user_roles",
|
|
["role_id"],
|
|
unique=False,
|
|
)
|
|
|
|
# Seed default roles
|
|
roles_table = table(
|
|
"roles",
|
|
column("id", sa.Integer()),
|
|
column("name", sa.String()),
|
|
column("display_name", sa.String()),
|
|
column("description", sa.Text()),
|
|
)
|
|
|
|
op.bulk_insert(
|
|
roles_table,
|
|
[
|
|
{
|
|
"id": 1,
|
|
"name": "admin",
|
|
"display_name": "Administrator",
|
|
"description": "Full platform access with user management rights.",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"name": "project_manager",
|
|
"display_name": "Project Manager",
|
|
"description": "Manage projects, scenarios, and associated data.",
|
|
},
|
|
{
|
|
"id": 3,
|
|
"name": "analyst",
|
|
"display_name": "Analyst",
|
|
"description": "Review dashboards and scenario outputs.",
|
|
},
|
|
{
|
|
"id": 4,
|
|
"name": "viewer",
|
|
"display_name": "Viewer",
|
|
"description": "Read-only access to assigned projects and reports.",
|
|
},
|
|
],
|
|
)
|
|
|
|
admin_password_hash = password_context.hash("ChangeMe123!")
|
|
|
|
users_table = table(
|
|
"users",
|
|
column("id", sa.Integer()),
|
|
column("email", sa.String()),
|
|
column("username", sa.String()),
|
|
column("password_hash", sa.String()),
|
|
column("is_active", sa.Boolean()),
|
|
column("is_superuser", sa.Boolean()),
|
|
)
|
|
|
|
op.bulk_insert(
|
|
users_table,
|
|
[
|
|
{
|
|
"id": 1,
|
|
"email": "admin@calminer.local",
|
|
"username": "admin",
|
|
"password_hash": admin_password_hash,
|
|
"is_active": True,
|
|
"is_superuser": True,
|
|
}
|
|
],
|
|
)
|
|
|
|
user_roles_table = table(
|
|
"user_roles",
|
|
column("user_id", sa.Integer()),
|
|
column("role_id", sa.Integer()),
|
|
column("granted_by", sa.Integer()),
|
|
)
|
|
|
|
op.bulk_insert(
|
|
user_roles_table,
|
|
[
|
|
{
|
|
"user_id": 1,
|
|
"role_id": 1,
|
|
"granted_by": 1,
|
|
}
|
|
],
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ix_user_roles_role_id", table_name="user_roles")
|
|
op.drop_table("user_roles")
|
|
|
|
op.drop_table("roles")
|
|
|
|
op.drop_index("ix_users_active_superuser", table_name="users")
|
|
op.drop_table("users")
|