Metadata-Version: 2.4
Name: aa-skip-email
Version: 0.4.0
Summary: Allows you to skip entering your email after registering via EVE SSO
Keywords: allianceauth,eveonline,django,sso,email
Author: Boris Talovikov
Author-email: Boris Talovikov <boris@talovikov.ru>
License-Expression: GPL-3.0-or-later
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Web Environment
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.2
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Internet :: WWW/HTTP
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
Requires-Dist: allianceauth>=4.0,<6
Maintainer: Boris Blade Artrald
Maintainer-email: Boris Blade Artrald <boris-blade-artrald@eveo7.ru>
Requires-Python: >=3.12
Project-URL: Homepage, https://gitlab.com/zima-corp/aa-skip-email
Project-URL: Repository, https://gitlab.com/zima-corp/aa-skip-email
Project-URL: Documentation, https://gitlab.com/zima-corp/aa-skip-email#readme
Project-URL: Issues, https://gitlab.com/zima-corp/aa-skip-email/-/issues
Project-URL: Changelog, https://gitlab.com/zima-corp/aa-skip-email/-/blob/main/CHANGELOG.md
Description-Content-Type: text/markdown

# AA Skip Email (aa_skip_email)

A small Alliance Auth plugin that lets you skip collecting real email addresses
during EVE SSO registration by automatically generating a placeholder `User.email`.
This is useful for integrations (e.g., OIDC clients) that expect `email` to be present.

---

## Features

- Creates a placeholder email for newly created users during SSO registration.
- Includes a one-off Django management command to backfill missing emails.
- Includes a Celery task suitable for periodic execution to keep data consistent.
- Emits a `placeholder_email_replaced` signal when a user's placeholder is
  replaced with a real address — downstream plugins (OIDC providers, audit
  logs, welcome flows) can react without polling.
- Exposes `is_placeholder_email()` and `get_placeholder_users()` helpers so
  callers can classify addresses without re-implementing the domain check.
- Ships a `PlaceholderEmailFilter` for the Django admin user list.
- Translated user-visible strings (admin filter, command output,
  AppConfig name) — currently `en` source + `ru` catalogue.

---

## Requirements

- Python >= 3.12 (tested on 3.12 and 3.13)
- Alliance Auth >= 4.0, < 6 (tested against the latest AA 4.x and AA 5.x)

---

## Installation

Install the plugin into the same Python environment as Alliance Auth (pip/uv/poetry-whatever you use).

```bash
pip install aa-skip-email
```

Add the app to `INSTALLED_APPS` (typically in `local.py`):

```python
INSTALLED_APPS += [
    ...
    "aa_skip_email",
    ...
]
```

Add the authentication backend to `AUTHENTICATION_BACKENDS`.

> [!IMPORTANT]
> This plugin replaces Alliance Auth’s default `allianceauth.authentication.backends.StateBackend` behavior.
> Update `AUTHENTICATION_BACKENDS` so that `SkipEmailBackend` is used instead of the original AA StateBackend.

Example:

```python
AUTHENTICATION_BACKENDS = [
    # Replace the original AA StateBackend with this plugin backend
    "aa_skip_email.authentication.backends.SkipEmailBackend",
    # Keep the rest of your original backends
    "django.contrib.auth.backends.ModelBackend",
]
```

---

## Configuration

Configure these in `local.py` (or your equivalent settings file).

### `AA_SKIP_EMAIL_DOMAIN`

Domain used for placeholder addresses.

- Type: `str`
- Default: `no-email.invalid`

```python
AA_SKIP_EMAIL_DOMAIN = "no-email.invalid"
```

Using a domain under `.invalid` helps avoid accidentally generating addresses at a real domain.

The plugin validates this setting at process start and raises
`ImproperlyConfigured` if the value is empty, contains `@` or whitespace,
or has no `.` (single-label "domain"). This catches misconfigurations
before the first SSO registration attempt.

### `AA_SKIP_EMAIL_LIMIT`

Default limit for how many users are processed per run by the management command and the Celery task.

- Type: `int`
- Default: `5000`

```python
AA_SKIP_EMAIL_LIMIT = 5000
```

---

## How placeholder emails are generated

A placeholder email is generated from the user’s username
(sanitized to a conservative character set) and a unique suffix
(typically the user id; otherwise a UUID).
The local-part is kept within 64 characters.

Resulting format is similar to:

```code
<sanitized-username>-<id-or-uuid>@<AA_SKIP_EMAIL_DOMAIN>
```

---

## One-off backfill (management command)

The plugin provides a Django management command to fill missing emails.

### Fill missing emails (default)

```bash
python manage.py fill_missing_emails
```

### Limit number of users

```bash
python manage.py fill_missing_emails --limit 5000
```

### Dry-run (no writes)

```bash
python manage.py fill_missing_emails --dry-run --limit 50
```

### Overwrite emails for all users

> [!CAUTION]
> This will replace existing emails.

```bash
python manage.py fill_missing_emails --overwrite
```

### Overwrite only existing placeholders

Safer if some users have real emails and you only want to regenerate placeholders.

```bash
python manage.py fill_missing_emails --overwrite --only-placeholders
```

---

## Scheduler (Celery Beat)

The plugin includes a Celery task named:

- `aa_skip_email.fill_missing_emails`

To run it periodically, add a schedule entry in `local.py` using `CELERYBEAT_SCHEDULE`.

### Example: run every 6 hours

```python
from celery.schedules import crontab

CELERYBEAT_SCHEDULE["aa_skip_email_fill_missing_emails"] = {
    "task": "aa_skip_email.fill_missing_emails",
    "schedule": crontab(minute=0, hour="*/6"),
    # Optional: helps spread load across instances in some AA deployments
    "apply_offset": True,
}
```

### Example: run daily at 04:15

```python
from celery.schedules import crontab

CELERYBEAT_SCHEDULE["aa_skip_email_fill_missing_emails"] = {
    "task": "aa_skip_email.fill_missing_emails",
    "schedule": crontab(minute=15, hour=4),
    "apply_offset": True,
}
```

---

## Public API for downstream plugins

### `is_placeholder_email(email)`

```python
from aa_skip_email import is_placeholder_email

if is_placeholder_email(user.email):
    ...  # treat as unverified / non-routable
```

Centralised classifier — returns `True` for any address produced by
this plugin (case-insensitive domain match, anchored to the domain
part). Falsy values (`None`, `""`) return `False`.

### `get_placeholder_users()`

```python
from aa_skip_email.helpers import get_placeholder_users

count = get_placeholder_users().count()
```

ORM-side analogue of `is_placeholder_email`. Returns a queryset of
all users whose `email` is a plugin-generated placeholder. Useful
for admin reports, audit dashboards, or batch follow-up.

### Admin filter

Add `PlaceholderEmailFilter` to AA's existing `UserAdmin.list_filter`
to surface a "Placeholder / Real / Blank" sidebar in the admin user
list. The plugin does **not** re-register the User model so AA's
custom UserAdmin remains intact:

```python
# in your local.py
from django.contrib import admin
from django.contrib.auth import get_user_model
from aa_skip_email.admin import PlaceholderEmailFilter

User = get_user_model()
UserAdmin = admin.site._registry[User].__class__
UserAdmin.list_filter = (*UserAdmin.list_filter, PlaceholderEmailFilter)
```

### Signal `placeholder_email_replaced`

Sent when `User.email` transitions from a plugin-generated placeholder
to a real address. Useful for OIDC providers that flip
`email_verified=true` on the ID token, audit logs, welcome emails, etc.

```python
from django.dispatch import receiver
from aa_skip_email.signals import placeholder_email_replaced

@receiver(placeholder_email_replaced, dispatch_uid="my_app.email_verified")
def on_email_replaced(sender, user, old_email, new_email, **kwargs):
    ...
```

The signal does **not** fire on:

- Initial fill of a blank/NULL email (placeholder is the new value).
- Placeholder rewritten as another placeholder (`--overwrite
  --only-placeholders`).
- Real email replaced with another real email.

---

## See also

- [allianceauth/issues/1286](https://gitlab.com/allianceauth/allianceauth/-/issues/1286)
- [allianceauth.allianceauth.backends](https://gitlab.com/allianceauth/allianceauth/-/blob/master/allianceauth/authentication/backends.py)
- [github.com/allianceauth/allianceauth/issues/1060](https://github.com/allianceauth/allianceauth/issues/1060)
