magic-link


Namemagic-link JSON
Version 1.0.0 PyPI version JSON
download
home_pageNone
SummaryModular, framework-agnostic passwordless authentication engine.
upload_time2025-10-19 18:17:27
maintainerNone
docs_urlNone
authormagic-link maintainers
requires_python>=3.8
licenseMIT
keywords authentication magic-link security passwordless python
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # magic_link

[![PyPI](https://img.shields.io/pypi/v/magic-link.svg)](https://pypi.org/project/magic-link/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![CI](https://github.com/h8v6/magic-link/actions/workflows/ci.yml/badge.svg)](https://github.com/h8v6/magic-link/actions/workflows/ci.yml)
[![Coverage](https://img.shields.io/badge/Coverage-100%25-brightgreen.svg)](#testing)

A modular, framework-agnostic engine for delivering passwordless authentication via secure magic links. The project follows a β€œminimal core + opt-in extras” philosophy so you can embed the library inside any Python stack without surrendering control of storage, email, or infrastructure.

> **Docs live in [`/docs`](docs/)**. This README highlights the essentials; detailed guides, recipes, and release instructions are all linked below.

---

## Table of Contents

- [Features](#features)
- [Requirements](#requirements)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [Extras](#extras)
- [CLI Utilities](#cli-utilities)
- [Documentation](#documentation)
- [Testing](#testing)
- [Release Process](#release-process)
- [Contributing](#contributing)
- [License](#license)

## Features

- πŸ” **Secure token engine** – deterministic HMAC signing, hashing, TTL enforcement, property-based tests.
- πŸ—ƒοΈ **Pluggable storage** – in-memory for dev, SQLAlchemy for RDBMS, Redis for low-latency workloads.
- βœ‰οΈ **Mailer registry** – SMTP implementation plus extensible template hooks.
- πŸŽ›οΈ **Service facade** – `MagicLinkService` coordinates issuance, verification, rate limiting, and single-use guarantees.
- πŸ› οΈ **CLI helpers** – `magic-link generate-config` and `magic-link test-email` streamline setup and diagnostics.
- πŸ“š **Extensive documentation** – quickstarts, framework recipes (FastAPI, Flask), and in-depth guides for email, migrations, and security.
- βœ… **100% test coverage** – unit, integration, and property-based suites validated in CI against live PostgreSQL, Redis, and audo SMTP servers.

## Requirements

- Python 3.8+
- Optional services depending on extras:
  - PostgreSQL (or any SQLAlchemy-supported database) for the SQL backend
  - Redis 5+ for the Redis backend
  - SMTP relay for outbound email

## Installation

Core library:

```bash
pip install magic-link
```

With common extras:

```bash
pip install "magic-link[sqlalchemy,redis,smtp]"
```

Add `cli` if you want the command-line utilities bundled:

```bash
pip install "magic-link[sqlalchemy,redis,smtp,cli]"
```

## Quick Start

1. Generate a configuration template:
   ```bash
   magic-link generate-config -o .env
   ```
2. Fill in the environment variables (secret key, SMTP credentials, database connection, etc.).
3. Wire the library into your framework. For FastAPI:

   ```python
   from fastapi import FastAPI, HTTPException
   from fastapi.responses import JSONResponse

   from magic_link import MagicLinkConfig, MagicLinkService
   from magic_link.interfaces import MagicLinkMessage
   from magic_link.mailer import create_mailer
   from magic_link.storage.redis import RedisStorage

   app = FastAPI()
   config = MagicLinkConfig.from_env()
   storage = RedisStorage.from_url("redis://localhost:6379/0")  # see docs for helper
   service = MagicLinkService(config=config, storage=storage)
   mailer = create_mailer(config)

   @app.post("/auth/magic-link")
   async def issue(payload: dict[str, str]) -> JSONResponse:
       email = payload.get("email")
       if not email:
           raise HTTPException(status_code=400, detail="Email is required")
       service.enforce_rate_limit(email)
       issued = service.issue_token(email)
       link = f"{config.base_url}{config.login_path}?token={issued.token}"
       mailer.send_magic_link(MagicLinkMessage(recipient=email, link=link, subject="Sign in", expires_at=issued.expires_at))
       return JSONResponse({"status": "sent"})
   ```

4. Verify tokens using `service.verify_token(...)` in your callback endpoint.

For a complete, copy-pasteable example (including Flask), consult the [Quickstart guide](docs/quickstart.md) and [recipes](docs/recipes/).

## Extras

| Extra        | Installs            | Purpose                                          |
|--------------|--------------------|--------------------------------------------------|
| `sqlalchemy` | `SQLAlchemy`, `psycopg[binary]` | Persistent token storage in relational DBs |
| `redis`      | `redis`, `hiredis`  | High-throughput storage + rate limiting          |
| `smtp`       | `email-validator`   | SMTP mailer backend and email utilities          |
| `cli`        | `click`             | Command-line helpers (`magic-link` CLI)          |

## CLI Utilities

```bash
# Print an annotated configuration template
magic-link generate-config

# Send a test email using your configured mailer
magic-link test-email user@example.com
```

## Documentation

- [Overview & FastAPI quickstart](docs/README.md)
- [Step-by-step Quickstart](docs/quickstart.md)
- [Framework recipes](docs/recipes/) – Flask and more
- [Guides](docs/guides/) – email templates, database migrations, security considerations
- [Architecture](docs/architecture.md)
- [Release process](docs/release.md)

## Testing

```bash
pip install -e ".[dev,sqlalchemy,redis,smtp,cli]"
pytest --cov=magic_link
```

CI runs the full suite (unit, property-based, integration) against PostgreSQL, Redis, and a local SMTP server. Coverage must stay β‰₯95% (currently 100%).

## Release Process

1. Follow the checklist in [docs/release.md](docs/release.md) (trusted publisher + semver workflow).
2. Alpha / beta versions: bump `pyproject.toml`, update `CHANGELOG.md`, tag, and publish.
3. Official releases are cut via GitHub Releases, which triggers the trusted publisher workflow to upload builds to PyPI.

## Contributing

- Open an issue or draft PR for discussion.
- Ensure tests and linters pass (`pytest`, `ruff check`, `black --check`, `mypy`).
- Update documentation for user-facing changes.

## License

Distributed under the [MIT License](LICENSE).

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "magic-link",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": null,
    "keywords": "authentication, magic-link, security, passwordless, python",
    "author": "magic-link maintainers",
    "author_email": null,
    "download_url": "https://files.pythonhosted.org/packages/3a/08/99c55a4c7b85bcb251c72b93fa5d60bd93f073fc3f0e1377fbac2c57c7d1/magic_link-1.0.0.tar.gz",
    "platform": null,
    "description": "# magic_link\n\n[![PyPI](https://img.shields.io/pypi/v/magic-link.svg)](https://pypi.org/project/magic-link/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![CI](https://github.com/h8v6/magic-link/actions/workflows/ci.yml/badge.svg)](https://github.com/h8v6/magic-link/actions/workflows/ci.yml)\n[![Coverage](https://img.shields.io/badge/Coverage-100%25-brightgreen.svg)](#testing)\n\nA modular, framework-agnostic engine for delivering passwordless authentication via secure magic links. The project follows a \u201cminimal core + opt-in extras\u201d philosophy so you can embed the library inside any Python stack without surrendering control of storage, email, or infrastructure.\n\n> **Docs live in [`/docs`](docs/)**. This README highlights the essentials; detailed guides, recipes, and release instructions are all linked below.\n\n---\n\n## Table of Contents\n\n- [Features](#features)\n- [Requirements](#requirements)\n- [Installation](#installation)\n- [Quick Start](#quick-start)\n- [Extras](#extras)\n- [CLI Utilities](#cli-utilities)\n- [Documentation](#documentation)\n- [Testing](#testing)\n- [Release Process](#release-process)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- \ud83d\udd10 **Secure token engine** \u2013 deterministic HMAC signing, hashing, TTL enforcement, property-based tests.\n- \ud83d\uddc3\ufe0f **Pluggable storage** \u2013 in-memory for dev, SQLAlchemy for RDBMS, Redis for low-latency workloads.\n- \u2709\ufe0f **Mailer registry** \u2013 SMTP implementation plus extensible template hooks.\n- \ud83c\udf9b\ufe0f **Service facade** \u2013 `MagicLinkService` coordinates issuance, verification, rate limiting, and single-use guarantees.\n- \ud83d\udee0\ufe0f **CLI helpers** \u2013 `magic-link generate-config` and `magic-link test-email` streamline setup and diagnostics.\n- \ud83d\udcda **Extensive documentation** \u2013 quickstarts, framework recipes (FastAPI, Flask), and in-depth guides for email, migrations, and security.\n- \u2705 **100% test coverage** \u2013 unit, integration, and property-based suites validated in CI against live PostgreSQL, Redis, and audo SMTP servers.\n\n## Requirements\n\n- Python 3.8+\n- Optional services depending on extras:\n  - PostgreSQL (or any SQLAlchemy-supported database) for the SQL backend\n  - Redis 5+ for the Redis backend\n  - SMTP relay for outbound email\n\n## Installation\n\nCore library:\n\n```bash\npip install magic-link\n```\n\nWith common extras:\n\n```bash\npip install \"magic-link[sqlalchemy,redis,smtp]\"\n```\n\nAdd `cli` if you want the command-line utilities bundled:\n\n```bash\npip install \"magic-link[sqlalchemy,redis,smtp,cli]\"\n```\n\n## Quick Start\n\n1. Generate a configuration template:\n   ```bash\n   magic-link generate-config -o .env\n   ```\n2. Fill in the environment variables (secret key, SMTP credentials, database connection, etc.).\n3. Wire the library into your framework. For FastAPI:\n\n   ```python\n   from fastapi import FastAPI, HTTPException\n   from fastapi.responses import JSONResponse\n\n   from magic_link import MagicLinkConfig, MagicLinkService\n   from magic_link.interfaces import MagicLinkMessage\n   from magic_link.mailer import create_mailer\n   from magic_link.storage.redis import RedisStorage\n\n   app = FastAPI()\n   config = MagicLinkConfig.from_env()\n   storage = RedisStorage.from_url(\"redis://localhost:6379/0\")  # see docs for helper\n   service = MagicLinkService(config=config, storage=storage)\n   mailer = create_mailer(config)\n\n   @app.post(\"/auth/magic-link\")\n   async def issue(payload: dict[str, str]) -> JSONResponse:\n       email = payload.get(\"email\")\n       if not email:\n           raise HTTPException(status_code=400, detail=\"Email is required\")\n       service.enforce_rate_limit(email)\n       issued = service.issue_token(email)\n       link = f\"{config.base_url}{config.login_path}?token={issued.token}\"\n       mailer.send_magic_link(MagicLinkMessage(recipient=email, link=link, subject=\"Sign in\", expires_at=issued.expires_at))\n       return JSONResponse({\"status\": \"sent\"})\n   ```\n\n4. Verify tokens using `service.verify_token(...)` in your callback endpoint.\n\nFor a complete, copy-pasteable example (including Flask), consult the [Quickstart guide](docs/quickstart.md) and [recipes](docs/recipes/).\n\n## Extras\n\n| Extra        | Installs            | Purpose                                          |\n|--------------|--------------------|--------------------------------------------------|\n| `sqlalchemy` | `SQLAlchemy`, `psycopg[binary]` | Persistent token storage in relational DBs |\n| `redis`      | `redis`, `hiredis`  | High-throughput storage + rate limiting          |\n| `smtp`       | `email-validator`   | SMTP mailer backend and email utilities          |\n| `cli`        | `click`             | Command-line helpers (`magic-link` CLI)          |\n\n## CLI Utilities\n\n```bash\n# Print an annotated configuration template\nmagic-link generate-config\n\n# Send a test email using your configured mailer\nmagic-link test-email user@example.com\n```\n\n## Documentation\n\n- [Overview & FastAPI quickstart](docs/README.md)\n- [Step-by-step Quickstart](docs/quickstart.md)\n- [Framework recipes](docs/recipes/) \u2013 Flask and more\n- [Guides](docs/guides/) \u2013 email templates, database migrations, security considerations\n- [Architecture](docs/architecture.md)\n- [Release process](docs/release.md)\n\n## Testing\n\n```bash\npip install -e \".[dev,sqlalchemy,redis,smtp,cli]\"\npytest --cov=magic_link\n```\n\nCI runs the full suite (unit, property-based, integration) against PostgreSQL, Redis, and a local SMTP server. Coverage must stay \u226595% (currently 100%).\n\n## Release Process\n\n1. Follow the checklist in [docs/release.md](docs/release.md) (trusted publisher + semver workflow).\n2. Alpha / beta versions: bump `pyproject.toml`, update `CHANGELOG.md`, tag, and publish.\n3. Official releases are cut via GitHub Releases, which triggers the trusted publisher workflow to upload builds to PyPI.\n\n## Contributing\n\n- Open an issue or draft PR for discussion.\n- Ensure tests and linters pass (`pytest`, `ruff check`, `black --check`, `mypy`).\n- Update documentation for user-facing changes.\n\n## License\n\nDistributed under the [MIT License](LICENSE).\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Modular, framework-agnostic passwordless authentication engine.",
    "version": "1.0.0",
    "project_urls": {
        "Documentation": "https://github.com/h8v6/magic-link/tree/main/docs",
        "Homepage": "https://github.com/h8v6/magic-link",
        "Issues": "https://github.com/h8v6/magic-link/issues",
        "Repository": "https://github.com/h8v6/magic-link"
    },
    "split_keywords": [
        "authentication",
        " magic-link",
        " security",
        " passwordless",
        " python"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "48920f00e86bb7d621c22375ae4644e7fb51b477a464c9450e105f0c1851536c",
                "md5": "ea1e75e3b29ac320d211643ff4d890a1",
                "sha256": "42dcf7a2f92cac1f2cd196505afcdb10cc1f518a7d29511cea29e51f03a2b7d9"
            },
            "downloads": -1,
            "filename": "magic_link-1.0.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "ea1e75e3b29ac320d211643ff4d890a1",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 22072,
            "upload_time": "2025-10-19T18:17:26",
            "upload_time_iso_8601": "2025-10-19T18:17:26.628447Z",
            "url": "https://files.pythonhosted.org/packages/48/92/0f00e86bb7d621c22375ae4644e7fb51b477a464c9450e105f0c1851536c/magic_link-1.0.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "3a0899c55a4c7b85bcb251c72b93fa5d60bd93f073fc3f0e1377fbac2c57c7d1",
                "md5": "0a4be4ddca3009566627e05520ac42ea",
                "sha256": "2236eed2e8d3952df1834abee6338d8c85f64b42b90129df47edf4aa6eeb2b19"
            },
            "downloads": -1,
            "filename": "magic_link-1.0.0.tar.gz",
            "has_sig": false,
            "md5_digest": "0a4be4ddca3009566627e05520ac42ea",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 24942,
            "upload_time": "2025-10-19T18:17:27",
            "upload_time_iso_8601": "2025-10-19T18:17:27.927224Z",
            "url": "https://files.pythonhosted.org/packages/3a/08/99c55a4c7b85bcb251c72b93fa5d60bd93f073fc3f0e1377fbac2c57c7d1/magic_link-1.0.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-19 18:17:27",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "h8v6",
    "github_project": "magic-link",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "magic-link"
}
        
Elapsed time: 0.78776s