From f858a1a78c7d1e8d516005ffc9aa5b44273ac07e Mon Sep 17 00:00:00 2001 From: tuxcoder Date: Mon, 25 Dec 2023 17:28:09 +0100 Subject: [PATCH] add basic passkey management --- .../versions/b5448df204eb_passkey.py | 40 ++++ lenticular_cloud/model.py | 18 +- lenticular_cloud/template/base.html.j2 | 4 +- .../template/frontend/base.html.j2 | 3 +- .../template/frontend/passkey_list.html.j2 | 30 +++ .../template/frontend/passkey_new.html.j2 | 69 +++++++ .../template/frontend/webauthn_list.html | 30 --- .../template/frontend/webauthn_register.html | 140 ------------- lenticular_cloud/views/frontend.py | 193 ++++++++++-------- overlay.nix | 4 +- 10 files changed, 258 insertions(+), 273 deletions(-) create mode 100644 lenticular_cloud/migrations/versions/b5448df204eb_passkey.py create mode 100644 lenticular_cloud/template/frontend/passkey_list.html.j2 create mode 100644 lenticular_cloud/template/frontend/passkey_new.html.j2 delete mode 100644 lenticular_cloud/template/frontend/webauthn_list.html delete mode 100644 lenticular_cloud/template/frontend/webauthn_register.html diff --git a/lenticular_cloud/migrations/versions/b5448df204eb_passkey.py b/lenticular_cloud/migrations/versions/b5448df204eb_passkey.py new file mode 100644 index 0000000..e7ff0ad --- /dev/null +++ b/lenticular_cloud/migrations/versions/b5448df204eb_passkey.py @@ -0,0 +1,40 @@ +"""passkey + +Revision ID: b5448df204eb +Revises: a74320a5d7a1 +Create Date: 2023-12-25 00:13:01.703575 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = 'b5448df204eb' +down_revision = 'a74320a5d7a1' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('passkey_credential', + sa.Column('id', sa.Integer(), autoincrement=True, nullable=False), + sa.Column('user_id', sa.Uuid(), nullable=False), + sa.Column('credential_id', sa.BINARY(), nullable=False), + sa.Column('credential_public_key', sa.BINARY(), nullable=False), + sa.Column('name', sa.String(length=250), nullable=False), + sa.Column('last_used', sa.DateTime(), nullable=True), + sa.Column('sign_count', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('modified_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('passkey_credential') + # ### end Alembic commands ### diff --git a/lenticular_cloud/model.py b/lenticular_cloud/model.py index acfbf0f..9da828f 100644 --- a/lenticular_cloud/model.py +++ b/lenticular_cloud/model.py @@ -164,14 +164,11 @@ class User(BaseModel, ModelUpdatedMixin): app_tokens: Mapped[List['AppToken']] = relationship('AppToken', back_populates='user') # totps: Mapped[List['Totp']] = relationship('Totp', back_populates='user', default_factory=list) - # webauthn_credentials: Mapped[List['WebauthnCredential']] = relationship('WebauthnCredential', back_populates='user', cascade='delete,delete-orphan', passive_deletes=True, default_factory=list) + passkey_credentials: Mapped[List['PasskeyCredential']] = relationship('PasskeyCredential', back_populates='user', cascade='delete,delete-orphan', passive_deletes=True) @property def totps(self) -> List['Totp']: return [] - @property - def webauthn_credentials(self) -> List['WebauthnCredential']: - return [] def __init__(self, **kwargs) -> None: super().__init__(**kwargs) @@ -245,17 +242,18 @@ class Totp(BaseModel, ModelUpdatedMixin): return totp.verify(token) -class WebauthnCredential(BaseModel, ModelUpdatedMixin): # pylint: disable=too-few-public-methods - """Webauthn credential model""" +class PasskeyCredential(BaseModel, ModelUpdatedMixin): # pylint: disable=too-few-public-methods + """Passkey credential model""" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[uuid.UUID] = mapped_column(db.Uuid, db.ForeignKey('user.id', ondelete='CASCADE'), nullable=False) - user_handle: Mapped[str] = mapped_column(db.String(64), nullable=False) - credential_data: Mapped[bytes] = mapped_column(db.LargeBinary, nullable=False) + credential_id: Mapped[bytes] = mapped_column(db.BINARY, nullable=False) + credential_public_key: Mapped[bytes] = mapped_column(db.BINARY, nullable=False) name: Mapped[str] = mapped_column(db.String(250), nullable=False) - registered: Mapped[datetime] = mapped_column(db.DateTime, default=datetime.utcnow, nullable=False) + last_used: Mapped[Optional[datetime]] = mapped_column(db.DateTime, nullable=True, default=None) + sign_count: Mapped[int] = mapped_column(db.Integer, nullable=False, default=0) - # user = db.relationship('User', back_populates='webauthn_credentials') + user = db.relationship('User', back_populates='passkey_credentials') class Group(BaseModel, ModelUpdatedMixin): diff --git a/lenticular_cloud/template/base.html.j2 b/lenticular_cloud/template/base.html.j2 index b507d82..9277d59 100644 --- a/lenticular_cloud/template/base.html.j2 +++ b/lenticular_cloud/template/base.html.j2 @@ -213,9 +213,9 @@ action_text - text of submit button class_ - sets a class for form #} -{% macro render_form(form, action_url='', action_text='Submit', class_='', btn_class='btn btn-primary', method='post', onsubmit='') -%} +{% macro render_form(form, action_url='', id='', action_text='Submit', class_='', btn_class='btn btn-primary', method='post', onsubmit='') -%} -
+ {{ _render_form(form) }} {% if not form.submit %} diff --git a/lenticular_cloud/template/frontend/base.html.j2 b/lenticular_cloud/template/frontend/base.html.j2 index 670613e..42ff924 100644 --- a/lenticular_cloud/template/frontend/base.html.j2 +++ b/lenticular_cloud/template/frontend/base.html.j2 @@ -17,7 +17,8 @@