Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ database: ## Creates an empty database

fixtures: ## Adds initial fixtures to the database
python scripts/add_fixtures.py
flask --app server db stamp

create_admin: ## Create initial admin user (usage: make create_admin EMAIL=admin@example.com PASSWORD=secretpassword)
flask --app server create-admin --email=$(EMAIL) --password=$(PASSWORD)
Expand Down
2 changes: 2 additions & 0 deletions api/app/db/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class User(db.Model, UserMixin):
id: Mapped[int] = mapped_column(primary_key=True)

email: Mapped[str] = mapped_column(String(100), unique=True, index=True)
name: Mapped[str | None] = mapped_column(String(100), nullable=True)
is_admin: Mapped[bool] = mapped_column(default=False)
hashed_password: Mapped[str] = mapped_column(String(256))

Expand Down Expand Up @@ -86,6 +87,7 @@ def totp_secret(self, totp_secret: str | None):
class UserSchema(Schema):
id = fields.Integer()
email = fields.String(validate=validate.Length(max=100))
name = fields.String(validate=validate.Length(max=100), allow_none=True)
is_admin = fields.Boolean()
is_verified = fields.Boolean(dump_only=True)
two_factor_enabled = fields.Boolean(dump_only=True)
3 changes: 2 additions & 1 deletion api/app/resources/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ def load_user(user_id):
class RegisterSchema(Schema):
email = fields.String(required=True)
password = fields.String(required=True)
name = fields.String(load_default=None)


@api.route("/register")
Expand All @@ -39,7 +40,7 @@ def post(self):
409,
)

new_user = User(email=data.get("email"))
new_user = User(email=data.get("email"), name=data.get("name"))
new_user.set_password(data.get("password"))

db.session.add(new_user)
Expand Down
29 changes: 29 additions & 0 deletions api/migrations/versions/6eba75463cb9_.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"""Added name column to user table

Revision ID: 6eba75463cb9
Revises: d29daf4c6bc4
Create Date: 2026-03-21 15:25:28.636095

"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "6eba75463cb9"
down_revision = "d29daf4c6bc4"
branch_labels = None
depends_on = None


def upgrade():
with op.batch_alter_table("user", schema=None) as batch_op:
batch_op.add_column(sa.Column("name", sa.String(length=100), nullable=True))

connection = op.get_bind()
connection.execute(sa.text("UPDATE user SET name = '-' WHERE name IS NULL"))


def downgrade():
with op.batch_alter_table("user", schema=None) as batch_op:
batch_op.drop_column("name")
215 changes: 109 additions & 106 deletions ui/cypress/e2e/admin.cy.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,127 +4,130 @@ describe('admin', () => {
})

it('Create new user', () => {
cy.visit('/')
cy.visit('/login')

cy.get('.hidden > :nth-child(1)').click();
cy.wait(100)

cy.wait(100);
cy.get('#email').clear()
cy.get('#email').type('admin@test.nl')
cy.get('#password').clear()
cy.get('#password').type('admin')
cy.get('button[type="submit"]').click()

cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('ad');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('admin');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.wait(100)

cy.wait(100);
cy.get('.text').click()
cy.get('p.text-success').click()
cy.get('thead > tr > .text-end > .btn').click()

cy.get('.text').click();
cy.get('p.text-success').click();
cy.get('thead > tr > .text-end > .btn').click();
cy.wait(100)

cy.wait(100);
cy.get('#email').clear()
cy.get('#email').type('test@test.nl')
cy.get('#password').clear()
cy.get('#password').type('Testing1!')
cy.get('.modal-action > .btn').click()

cy.get('#email').clear('te');
cy.get('#email').type('test@test.nl');
cy.get('#password').clear();
cy.get('#password').type('Testing1!');
cy.get('.modal-action > .btn').click();
cy.wait(100)

cy.wait(100);
cy.get('.modal-box .btn-circle').click()

cy.get(':nth-child(3) > summary.btn').click();
cy.wait(50);
cy.get('.menu > :nth-child(3) > .btn').click();
cy.wait(50);
cy.get('.hidden > :nth-child(1)').click();
cy.wait(50);
cy.get(':nth-child(3) > summary.btn').click()
cy.get('.fa-arrow-right-from-bracket').click({ force: true })
cy.wait(50)
cy.visit('/login')
cy.wait(50)

cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('test@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('Testing1!');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('#email').clear()
cy.get('#email').type('test@test.nl')
cy.get('#password').clear()
cy.get('#password').type('Testing1!')
cy.get('button[type="submit"]').click()

cy.wait(50);
cy.wait(50)

cy.url().should('include', '/home')
})

it('Create another admin', function() {
cy.visit('/');
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('admin');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get('p.text-success').click();
cy.get('thead > tr > .text-end').click();
cy.get('.modal-box').click();
cy.get('#email').clear('ad');
cy.get('#email').type('admin2@test.nl');
cy.get('#password').clear();
cy.get('#password').type('Testing1@');
cy.get('.checkbox').check();
cy.get('.modal-action > .btn').click();
cy.get('.text').click();
cy.get('.menu > :nth-child(3) > .btn').click();
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin2@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('Testing1@');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get(':nth-child(2) > .btn > .fa-solid').click();
it('Create another admin', function () {
cy.visit('/login')
cy.get('#email').clear()
cy.get('#email').type('admin@test.nl')
cy.get('#password').clear()
cy.get('#password').type('admin')
cy.get('button[type="submit"]').click()
cy.get('.text').click()
cy.get('p.text-success').click()
cy.get('thead > tr > .text-end').click()
cy.get('.modal-box').click()
cy.get('#email').clear()
cy.get('#email').type('admin2@test.nl')
cy.get('#password').clear()
cy.get('#password').type('Testing1@')
cy.get('.checkbox').check()
cy.get('.modal-action > .btn').click()
cy.get('.text').click()
cy.get('.fa-arrow-right-from-bracket').click({ force: true })

cy.visit('/login')
cy.get('#email').clear()
cy.get('#email').type('admin2@test.nl')
cy.get('#password').clear()
cy.get('#password').type('Testing1@')
cy.get('button[type="submit"]').click()
cy.get('.text').click()
cy.get(':nth-child(2) > .btn > .fa-solid').click()

cy.url().should('include', '/admin-panel')
});

it('Delete user', function() {
cy.visit('/');
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('ad');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('admin');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get('p.text-success').click();
cy.get('thead > tr > .text-end > .btn > .fa-solid').click();
cy.get('#email').clear('te');
cy.get('#email').type('test@test.nl');
cy.get('#password').clear();
cy.get('#password').type('Testing1!');
cy.get('.modal-action > .btn').click();
cy.get('.text').click();
cy.get('.menu > :nth-child(3) > .btn').click();
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('te');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('test@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('Testing1!');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get('[open=""] > .menu > :nth-child(2) > .btn').click();
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('ad');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('admin@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('admin');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();
cy.get('.text').click();
cy.get('p.text-success').click();
cy.get(':nth-child(2) > .text-end > .btn > .fa-solid').click();
cy.get('.btn-error').click();
cy.get('.text').click();
cy.get('.menu > :nth-child(3) > .btn > .fa-solid').click();
cy.get('.hidden > :nth-child(1)').click();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').clear('te');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(1) > .input > #email').type('test@test.nl');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').clear();
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > :nth-child(2) > .input > #password').type('Testing1!');
cy.get('.modal-open > .modal-box > form.w-full > .space-y-4 > .modal-action > .btn').click();

cy.get('.alert span').should('contain.text', 'Could not login with the given email and password');
});
})

it('Delete user', function () {
cy.visit('/login')
cy.get('#email').clear()
cy.get('#email').type('admin@test.nl')
cy.get('#password').clear()
cy.get('#password').type('admin')
cy.get('button[type="submit"]').click()
cy.get('.text').click()
cy.get('p.text-success').click()
cy.get('thead > tr > .text-end > .btn > .fa-solid').click()
cy.get('#email').clear()
cy.get('#email').type('test@test.nl')
cy.get('#password').clear()
cy.get('#password').type('Testing1!')
cy.get('.modal-action > .btn').click()
cy.get('.text').click()
cy.get('.fa-arrow-right-from-bracket').click({ force: true })
cy.visit('/login')
cy.get('#email').clear()
cy.get('#email').type('test@test.nl')
cy.get('#password').clear()
cy.get('#password').type('Testing1!')
cy.get('button[type="submit"]').click()
cy.get('.text').click()
cy.get('[open=""] > .menu > :nth-child(2) > .btn').click()
cy.visit('/login')
cy.get('#email').clear()
cy.get('#email').type('admin@test.nl')
cy.get('#password').clear()
cy.get('#password').type('admin')
cy.get('button[type="submit"]').click()
cy.get('.text').click()
cy.get('p.text-success').click()
cy.get(':nth-child(2) > .text-end > .btn > .fa-solid').click()
cy.get('.btn-error').click()
cy.get('.text').click()
cy.get('.fa-arrow-right-from-bracket').click({ force: true })
cy.visit('/login')
cy.get('#email').clear()
cy.get('#email').type('test@test.nl')
cy.get('#password').clear()
cy.get('#password').type('Testing1!')
cy.get('button[type="submit"]').click()

cy.get('.alert span').should(
'contain.text',
'Could not login with the given email and password'
)
})
})
Loading
Loading