reflex-local-auth


Namereflex-local-auth JSON
Version 0.2.2 PyPI version JSON
download
home_pageNone
SummaryLocal DB user authentication for Reflex apps
upload_time2024-08-07 18:47:39
maintainerNone
docs_urlNone
authorNone
requires_python>=3.8
licenseApache-2.0
keywords reflex reflex-custom-components
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # local-auth

Easy access to local authentication in your [Reflex](https://reflex.dev) app.

## Installation

```bash
pip install reflex-local-auth
```

## Usage

```python
import reflex_local_auth
```

### Add the canned login and registration pages

If you don't want to create your own login and registration forms, add the canned pages to your app:

```python
app = rx.App()
...
app.add_page(
    reflex_local_auth.pages.login_page,
    route=reflex_local_auth.routes.LOGIN_ROUTE,
    title="Login",
)
app.add_page(
    reflex_local_auth.pages.register_page,
    route=reflex_local_auth.routes.REGISTER_ROUTE,
    title="Register",
)
```

### Create Database Tables

```console
reflex db init  # if needed
reflex db makemigrations
reflex db migrate
```

### Redirect Pages to Login

Use the `@reflex_local_auth.require_login` decorator to redirect unauthenticated users to the LOGIN_ROUTE.

```python
@rx.page()
@reflex_local_auth.require_login
def need2login(request):
    return rx.heading("Accessing this page will redirect to the login page if not authenticated.")
```

Although this _seems_ to protect the content, it is still publicly accessible
when viewing the source code for the page! This should be considered a mechanism
to redirect users to the login page, NOT a way to protect data.

### Protect State

It is _extremely_ important to protect private data returned by State via Event
Handlers! All static page data should be considered public, the only data that
can truly be considered private at runtime must be fetched by an event handler
that checks the authenticated user and assigns the data to a State Var. After
the user logs out, the private data should be cleared and the user's tab should
be closed to destroy the session identifier.

```python
import reflex_local_auth


class ProtectedState(reflex_local_auth.LocalAuthState):
    data: str

    def on_load(self):
        if not self.is_authenticated:
            return reflex_local_auth.LoginState.redir
        self.data = f"This is truly private data for {self.authenticated_user.username}"

    def do_logout(self):
        self.data = ""
        return reflex_local_auth.LocalAuthState.do_logout


@rx.page(on_load=ProtectedState.on_load)
@reflex_local_auth.require_login
def protected_page():
    return rx.heading(ProtectedState.data)
```

## Customization

The basic `reflex_local_auth.LocalUser` model provides password hashing and
verification, and an enabled flag. Additional functionality can be added by
creating a new `UserInfo` model and creating a foreign key relationship to the
`user.id` field.

```python
import sqlmodel
import reflex as rx
import reflex_local_auth


class UserInfo(rx.Model, table=True):
    email: str
    is_admin: bool = False
    created_from_ip: str

    user_id: int = sqlmodel.Field(foreign_key="user.id")
```

To populate the extra fields, you can create a custom registration page and
state that asks for the extra info, or it can be added via other event handlers.

A custom registration state and form might look like:

```python
import reflex as rx
import reflex_local_auth
from reflex_local_auth.pages.components import input_100w, MIN_WIDTH, PADDING_TOP


class MyRegisterState(reflex_local_auth.RegistrationState):
    # This event handler must be named something besides `handle_registration`!!!
    def handle_registration_email(self, form_data):
        registration_result = super().handle_registration(form_data)
        if self.new_user_id >= 0:
            with rx.session() as session:
                session.add(
                    UserInfo(
                        email=form_data["email"],
                        created_from_ip=self.router.headers.get(
                            "x_forwarded_for",
                            self.router.session.client_ip,
                        ),
                        user_id=self.new_user_id,
                    )
                )
                session.commit()
        return registration_result


def register_error() -> rx.Component:
    """Render the registration error message."""
    return rx.cond(
        reflex_local_auth.RegistrationState.error_message != "",
        rx.callout(
            reflex_local_auth.RegistrationState.error_message,
            icon="triangle_alert",
            color_scheme="red",
            role="alert",
            width="100%",
        ),
    )


def register_form() -> rx.Component:
    """Render the registration form."""
    return rx.form(
        rx.vstack(
            rx.heading("Create an account", size="7"),
            register_error(),
            rx.text("Username"),
            input_100w("username"),
            rx.text("Email"),
            input_100w("email"),
            rx.text("Password"),
            input_100w("password", type="password"),
            rx.text("Confirm Password"),
            input_100w("confirm_password", type="password"),
            rx.button("Sign up", width="100%"),
            rx.center(
                rx.link("Login", on_click=lambda: rx.redirect(reflex_local_auth.routes.LOGIN_ROUTE)),
                width="100%",
            ),
            min_width=MIN_WIDTH,
        ),
        on_submit=MyRegisterState.handle_registration_email,
    )


def register_page() -> rx.Component:
    """Render the registration page.

    Returns:
        A reflex component.
    """

    return rx.center(
        rx.cond(
            reflex_local_auth.RegistrationState.success,
            rx.vstack(
                rx.text("Registration successful!"),
            ),
            rx.card(register_form()),
        ),
        padding_top=PADDING_TOP,
    )
```


Finally you can create a substate of `reflex_local_auth.LocalAuthState` which fetches
the associated `UserInfo` record and makes it available to your app.

```python
import sqlmodel
import reflex as rx
import reflex_local_auth


class MyLocalAuthState(reflex_local_auth.LocalAuthState):
    @rx.cached_var
    def authenticated_user_info(self) -> UserInfo | None:
        if self.authenticated_user.id < 0:
            return
        with rx.session() as session:
            return session.exec(
                sqlmodel.select(UserInfo).where(
                    UserInfo.user_id == self.authenticated_user.id
                ),
            ).one_or_none()


@rx.page()
@reflex_local_auth.require_login
def user_info():
    return rx.vstack(
        rx.text(f"Username: {MyLocalAuthState.authenticated_user.username}"),
        rx.cond(
            MyLocalAuthState.authenticated_user_info,
            rx.fragment(
                rx.text(f"Email: {MyLocalAuthState.authenticated_user_info.email}"),
                rx.text(f"Account Created From: {MyLocalAuthState.authenticated_user_info.created_from_ip}"),
            ),
        ),
    )
```

## Migrating from 0.0.x to 0.1.x

The `User` model has been renamed to `LocalUser` and the `AuthSession` model has
been renamed to `LocalAuthSession`. If your app was using reflex-local-auth 0.0.x,
then you will need to make manual changes to migration script to copy existing user
data into the new tables _after_ running `reflex db makemigrations`.

See [`local_auth_demo/alembic/version/cb01e050df85_.py`](local_auth_demo/alembic/version/cb01e050df85_.py) for an example migration script.

Importantly, your `upgrade` function should include the following lines, after creating
the new tables and before dropping the old tables:

```python
    op.execute("INSERT INTO localuser SELECT * FROM user;")
    op.execute("INSERT INTO localauthsession SELECT * FROM authsession;")
```

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "reflex-local-auth",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "reflex, reflex-custom-components",
    "author": null,
    "author_email": "Masen Furer <m_github@0x26.net>",
    "download_url": "https://files.pythonhosted.org/packages/39/c0/4d8f2f1303a1523d059a3e934153f816d86c3b7eaee699ec4f74ffefa073/reflex_local_auth-0.2.2.tar.gz",
    "platform": null,
    "description": "# local-auth\n\nEasy access to local authentication in your [Reflex](https://reflex.dev) app.\n\n## Installation\n\n```bash\npip install reflex-local-auth\n```\n\n## Usage\n\n```python\nimport reflex_local_auth\n```\n\n### Add the canned login and registration pages\n\nIf you don't want to create your own login and registration forms, add the canned pages to your app:\n\n```python\napp = rx.App()\n...\napp.add_page(\n    reflex_local_auth.pages.login_page,\n    route=reflex_local_auth.routes.LOGIN_ROUTE,\n    title=\"Login\",\n)\napp.add_page(\n    reflex_local_auth.pages.register_page,\n    route=reflex_local_auth.routes.REGISTER_ROUTE,\n    title=\"Register\",\n)\n```\n\n### Create Database Tables\n\n```console\nreflex db init  # if needed\nreflex db makemigrations\nreflex db migrate\n```\n\n### Redirect Pages to Login\n\nUse the `@reflex_local_auth.require_login` decorator to redirect unauthenticated users to the LOGIN_ROUTE.\n\n```python\n@rx.page()\n@reflex_local_auth.require_login\ndef need2login(request):\n    return rx.heading(\"Accessing this page will redirect to the login page if not authenticated.\")\n```\n\nAlthough this _seems_ to protect the content, it is still publicly accessible\nwhen viewing the source code for the page! This should be considered a mechanism\nto redirect users to the login page, NOT a way to protect data.\n\n### Protect State\n\nIt is _extremely_ important to protect private data returned by State via Event\nHandlers! All static page data should be considered public, the only data that\ncan truly be considered private at runtime must be fetched by an event handler\nthat checks the authenticated user and assigns the data to a State Var. After\nthe user logs out, the private data should be cleared and the user's tab should\nbe closed to destroy the session identifier.\n\n```python\nimport reflex_local_auth\n\n\nclass ProtectedState(reflex_local_auth.LocalAuthState):\n    data: str\n\n    def on_load(self):\n        if not self.is_authenticated:\n            return reflex_local_auth.LoginState.redir\n        self.data = f\"This is truly private data for {self.authenticated_user.username}\"\n\n    def do_logout(self):\n        self.data = \"\"\n        return reflex_local_auth.LocalAuthState.do_logout\n\n\n@rx.page(on_load=ProtectedState.on_load)\n@reflex_local_auth.require_login\ndef protected_page():\n    return rx.heading(ProtectedState.data)\n```\n\n## Customization\n\nThe basic `reflex_local_auth.LocalUser` model provides password hashing and\nverification, and an enabled flag. Additional functionality can be added by\ncreating a new `UserInfo` model and creating a foreign key relationship to the\n`user.id` field.\n\n```python\nimport sqlmodel\nimport reflex as rx\nimport reflex_local_auth\n\n\nclass UserInfo(rx.Model, table=True):\n    email: str\n    is_admin: bool = False\n    created_from_ip: str\n\n    user_id: int = sqlmodel.Field(foreign_key=\"user.id\")\n```\n\nTo populate the extra fields, you can create a custom registration page and\nstate that asks for the extra info, or it can be added via other event handlers.\n\nA custom registration state and form might look like:\n\n```python\nimport reflex as rx\nimport reflex_local_auth\nfrom reflex_local_auth.pages.components import input_100w, MIN_WIDTH, PADDING_TOP\n\n\nclass MyRegisterState(reflex_local_auth.RegistrationState):\n    # This event handler must be named something besides `handle_registration`!!!\n    def handle_registration_email(self, form_data):\n        registration_result = super().handle_registration(form_data)\n        if self.new_user_id >= 0:\n            with rx.session() as session:\n                session.add(\n                    UserInfo(\n                        email=form_data[\"email\"],\n                        created_from_ip=self.router.headers.get(\n                            \"x_forwarded_for\",\n                            self.router.session.client_ip,\n                        ),\n                        user_id=self.new_user_id,\n                    )\n                )\n                session.commit()\n        return registration_result\n\n\ndef register_error() -> rx.Component:\n    \"\"\"Render the registration error message.\"\"\"\n    return rx.cond(\n        reflex_local_auth.RegistrationState.error_message != \"\",\n        rx.callout(\n            reflex_local_auth.RegistrationState.error_message,\n            icon=\"triangle_alert\",\n            color_scheme=\"red\",\n            role=\"alert\",\n            width=\"100%\",\n        ),\n    )\n\n\ndef register_form() -> rx.Component:\n    \"\"\"Render the registration form.\"\"\"\n    return rx.form(\n        rx.vstack(\n            rx.heading(\"Create an account\", size=\"7\"),\n            register_error(),\n            rx.text(\"Username\"),\n            input_100w(\"username\"),\n            rx.text(\"Email\"),\n            input_100w(\"email\"),\n            rx.text(\"Password\"),\n            input_100w(\"password\", type=\"password\"),\n            rx.text(\"Confirm Password\"),\n            input_100w(\"confirm_password\", type=\"password\"),\n            rx.button(\"Sign up\", width=\"100%\"),\n            rx.center(\n                rx.link(\"Login\", on_click=lambda: rx.redirect(reflex_local_auth.routes.LOGIN_ROUTE)),\n                width=\"100%\",\n            ),\n            min_width=MIN_WIDTH,\n        ),\n        on_submit=MyRegisterState.handle_registration_email,\n    )\n\n\ndef register_page() -> rx.Component:\n    \"\"\"Render the registration page.\n\n    Returns:\n        A reflex component.\n    \"\"\"\n\n    return rx.center(\n        rx.cond(\n            reflex_local_auth.RegistrationState.success,\n            rx.vstack(\n                rx.text(\"Registration successful!\"),\n            ),\n            rx.card(register_form()),\n        ),\n        padding_top=PADDING_TOP,\n    )\n```\n\n\nFinally you can create a substate of `reflex_local_auth.LocalAuthState` which fetches\nthe associated `UserInfo` record and makes it available to your app.\n\n```python\nimport sqlmodel\nimport reflex as rx\nimport reflex_local_auth\n\n\nclass MyLocalAuthState(reflex_local_auth.LocalAuthState):\n    @rx.cached_var\n    def authenticated_user_info(self) -> UserInfo | None:\n        if self.authenticated_user.id < 0:\n            return\n        with rx.session() as session:\n            return session.exec(\n                sqlmodel.select(UserInfo).where(\n                    UserInfo.user_id == self.authenticated_user.id\n                ),\n            ).one_or_none()\n\n\n@rx.page()\n@reflex_local_auth.require_login\ndef user_info():\n    return rx.vstack(\n        rx.text(f\"Username: {MyLocalAuthState.authenticated_user.username}\"),\n        rx.cond(\n            MyLocalAuthState.authenticated_user_info,\n            rx.fragment(\n                rx.text(f\"Email: {MyLocalAuthState.authenticated_user_info.email}\"),\n                rx.text(f\"Account Created From: {MyLocalAuthState.authenticated_user_info.created_from_ip}\"),\n            ),\n        ),\n    )\n```\n\n## Migrating from 0.0.x to 0.1.x\n\nThe `User` model has been renamed to `LocalUser` and the `AuthSession` model has\nbeen renamed to `LocalAuthSession`. If your app was using reflex-local-auth 0.0.x,\nthen you will need to make manual changes to migration script to copy existing user\ndata into the new tables _after_ running `reflex db makemigrations`.\n\nSee [`local_auth_demo/alembic/version/cb01e050df85_.py`](local_auth_demo/alembic/version/cb01e050df85_.py) for an example migration script.\n\nImportantly, your `upgrade` function should include the following lines, after creating\nthe new tables and before dropping the old tables:\n\n```python\n    op.execute(\"INSERT INTO localuser SELECT * FROM user;\")\n    op.execute(\"INSERT INTO localauthsession SELECT * FROM authsession;\")\n```\n",
    "bugtrack_url": null,
    "license": "Apache-2.0",
    "summary": "Local DB user authentication for Reflex apps",
    "version": "0.2.2",
    "project_urls": {
        "Homepage": "https://github.com/masenf/reflex-local-auth"
    },
    "split_keywords": [
        "reflex",
        " reflex-custom-components"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "70028ecaf032e1b5ee351e2a2dd624559377d253c2a5280f7564bca6c1936fef",
                "md5": "dabdec7abfb464e704b2191e78eec4a5",
                "sha256": "9eba898cb9d5e53b2cbe7b56a56790c25dba58d28463411ed4729ce3af6fa729"
            },
            "downloads": -1,
            "filename": "reflex_local_auth-0.2.2-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "dabdec7abfb464e704b2191e78eec4a5",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 11818,
            "upload_time": "2024-08-07T18:47:36",
            "upload_time_iso_8601": "2024-08-07T18:47:36.311934Z",
            "url": "https://files.pythonhosted.org/packages/70/02/8ecaf032e1b5ee351e2a2dd624559377d253c2a5280f7564bca6c1936fef/reflex_local_auth-0.2.2-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "39c04d8f2f1303a1523d059a3e934153f816d86c3b7eaee699ec4f74ffefa073",
                "md5": "c94991f1425943cf3b4fa4a935c6db8e",
                "sha256": "56efc191343b0a07e01c92e8a23c5047a22aa269d21cef1bd48d96c48ada1b07"
            },
            "downloads": -1,
            "filename": "reflex_local_auth-0.2.2.tar.gz",
            "has_sig": false,
            "md5_digest": "c94991f1425943cf3b4fa4a935c6db8e",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 10028,
            "upload_time": "2024-08-07T18:47:39",
            "upload_time_iso_8601": "2024-08-07T18:47:39.030448Z",
            "url": "https://files.pythonhosted.org/packages/39/c0/4d8f2f1303a1523d059a3e934153f816d86c3b7eaee699ec4f74ffefa073/reflex_local_auth-0.2.2.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2024-08-07 18:47:39",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "masenf",
    "github_project": "reflex-local-auth",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "reflex-local-auth"
}
        
Elapsed time: 3.19490s