Metadata-Version: 2.4
Name: brsxmail
Version: 0.1.0
Summary: Closed-circuit / internal messaging system (FastAPI based, backend-ready)
Author: BRSX-Labs
License: MIT
Project-URL: Homepage, https://github.com/BRSX-Labs/brsxmail
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: fastapi>=0.110
Requires-Dist: uvicorn>=0.29
Requires-Dist: python-multipart>=0.0.9

# brsxmail

Closed-circuit / internal messaging system. FastAPI based, backend-ready.
The user can provide their own HTML interface or use the built-in
(simple, form-based) one.

> ⚠️ **Security note:** This package does not provide production-grade
> security on its own. Passwords are hashed with plain `sha256` (no
> salt), sessions are kept in RAM (lost on restart, not shared across
> workers), and there's no rate limiting/CORS/brute-force protection.
> This package **is designed to be used together with BRSX-Labs'
> `zerov4` security middleware** — bot/brute-force protection, session
> hijacking detection, and request filtering are meant to be handled
> by `zerov4`. Don't run `brsxmail` bare, in production, exposed to the
> internet, with sensitive data; use it behind `zerov4` as a layer, or
> on closed-circuit/internal networks.

## Installation

```bash
pip install brsxmail
```

or clone this repo and:

```bash
pip install -e .
```

## Usage

### Quick start

```bash
python run.py
```

- On first run, if `brsxmail.config.json` doesn't exist, an interactive
  setup wizard opens in the terminal: it asks for domain, port, host,
  storage type (json/sqlite), and data folder. You never have to write
  a config by hand.
- On subsequent runs, since config already exists, the wizard isn't
  asked again — the server starts directly.
- To reset settings:
  ```bash
  python run.py --reconfigure
  ```

### Via CLI after pip install

```bash
brsxmail
brsxmail --reconfigure
```

### The simplest usage

```python
from brsxmail import mail
mail.run()
```

### From code (advanced)

```python
from brsxmail import create_app, get_or_create_config
import uvicorn

config = get_or_create_config()
app = create_app(config)
uvicorn.run(app, host=config["host"], port=config["port"])
```

## Using your own HTML

The setup wizard asks you directly: *"Which interface do you want to
use?"* — the built-in default, or your own `index.html`. Your answer
is saved into `brsxmail.config.json` as `use_custom_html: true/false`;
the server doesn't silently decide on every startup, it follows this
setting.

- **If you chose "I'll use my own index.html":** you need to place an
  `index.html` file in the folder where you run the server. The server
  looks for it there on every request. If the file isn't there, it
  prints a clear warning to the terminal and falls back to the default
  interface (not silently).
- **If you chose "use the default":** it won't look at the working
  directory at all, even if there's an `index.html` there — it always
  uses the interface bundled with the package.

If you change your mind, run `python run.py --reconfigure` to re-run
the wizard and update your choice.

The server reads this file and fills in these placeholders for you:

- `{{LOGGED_IN}}` → `"true"` / `"false"`
- `{{USER}}` → the logged-in user's email (empty if not logged in)
- `{{DOMAIN}}` → the domain from config (e.g. `@brsx.com`)

## Writing your own interface: the API contract

When writing your own `index.html`, the JS side needs to follow these
rules. For every endpoint, the request type (form-data or JSON), the
expected fields, and the shape of the response are documented below.
If you don't use these names and types exactly, the backend won't
recognize your requests.

**General rule:** All `POST` endpoints expect **form-data**, not a JSON
body. All responses come back as JSON. After login, the session is
kept via a **cookie** (`session_id`) — using `credentials: "same-origin"`
(or the browser default) in your `fetch` calls is enough; you don't
need to carry the cookie manually.

---

### `POST /register` — Register

Request (form-data):
```js
const form = new FormData();
form.append("email", "ali@your-domain.com");
form.append("password", "1234");
await fetch("/register", { method: "POST", body: form });
```

Response:
```json
{ "ok": true }
```
or on error (400):
```json
{ "error": "User already exists" }
```

---

### `POST /login` — Login

Request (form-data), same fields as `register`: `email`, `password`.

Response (on success, the browser automatically gets the `session_id`
cookie):
```json
{ "ok": true }
```
On error (401):
```json
{ "error": "Invalid login" }
```

---

### `POST /logout` — Logout

Request: no body needed, just a `POST` request.

Response:
```json
{ "ok": true }
```

---

### `POST /send` — Send a message

Must be logged in (cookie is sent automatically).

Request (form-data):
```js
const form = new FormData();
form.append("receiver", "veli@your-domain.com");
form.append("content", "hello");
await fetch("/send", { method: "POST", body: form });
```

Response:
```json
{ "ok": true }
```
Errors: `401` (not logged in) or `400` (recipient not found).

---

### `GET /inbox` — Inbox

Request: no parameters, just `GET`.

Response — a **list** (array) of messages, each element shaped like:
```json
[
  {
    "id": "71b57f2c-...",
    "from": "ali@your-domain.com",
    "to": "veli@your-domain.com",
    "content": "hello",
    "time": "26.07.2026 09:56",
    "read": false
  }
]
```
If not logged in, returns `401` with `{ "error": "..." }` (not an array).

---

### `GET /message/{id}` — Open a message

Put the message's `id` field in place of `{id}`: `/message/71b57f2c-...`

Response — a single message **object** (not an array), marked as
`read: true`:
```json
{ "id": "...", "from": "...", "to": "...", "content": "...", "time": "...", "read": true }
```
`404` if not found.

---

### `DELETE /message/{id}` — Delete a message

```js
await fetch(`/message/${msgId}`, { method: "DELETE" });
```

Response:
```json
{ "ok": true }
```
`404` if not found or not authorized.

---

### `GET /search?q=...` — Search

Pass the query as the `q` parameter: `/search?q=hello`

Response — a message **list** (array), same shape as `/inbox`.

---

### `GET /unread-count` — Unread count

Response:
```json
{ "unread": 3 }
```

---

### Summary table

| Method | Path              | Body type   | Fields                 | Returns              |
|--------|-------------------|-------------|-------------------------|------------------------|
| GET    | `/`               | —           | —                       | HTML                   |
| POST   | `/register`       | form-data   | `email`, `password`     | `{ok}` / `{error}`     |
| POST   | `/login`          | form-data   | `email`, `password`     | `{ok}` / `{error}`     |
| POST   | `/logout`         | —           | —                       | `{ok}`                 |
| POST   | `/send`           | form-data   | `receiver`, `content`   | `{ok}` / `{error}`     |
| GET    | `/inbox`          | —           | —                       | message list           |
| GET    | `/message/{id}`   | —           | —                       | single message object  |
| DELETE | `/message/{id}`   | —           | —                       | `{ok}` / `{error}`     |
| GET    | `/search?q=...`   | —           | `q` (query param)       | message list           |
| GET    | `/unread-count`   | —           | —                       | `{unread: N}`          |

For a working example, check the bundled `brsxmail/webui/index.html` —
it has real, working JS examples of all these calls; use it as a
reference when writing your own interface.

## Using it together with zerov4

`brsxmail` doesn't include a security layer on its own; instead it's
designed to be **run behind BRSX-Labs' `zerov4` (ZeroxArx) security
middleware as a layer in front of it**. In practice that means:

- You put `zerov4` in front of the `brsxmail` FastAPI app and let it
  handle bot/brute-force protection, session hijacking detection, and
  suspicious request filtering.
- `brsxmail` focuses only on the messaging logic (register, login,
  send, inbox, search, delete); it doesn't harden itself against
  authentication attacks on its own.

### Setup

`mail.run()` starts and blocks on uvicorn by default — in that case
there's never a moment for `zerov4` to wrap the app. Instead, use
`blocking=False` to just get the ready FastAPI app, and hand the
server off to `zerov4`:

```python
# main.py (or whatever your app file is named)
from brsxmail import mail
from zerov4 import arx

app = mail.run(blocking=False)   # only creates the app, doesn't start the server
arx.run(app)                      # zerov4 wraps the app and starts the server itself
```

When run this way:
- The setup wizard is still asked on first run (`get_or_create_config`
  is called internally by `mail.run()`), and config is saved to disk.
- The server is now started by `zerov4`, not `uvicorn` directly;
  brute-force/bot/session-hijacking protection comes from the `zerov4`
  layer.
- `brsxmail`'s own `/register`, `/login`, `/send`, etc. endpoints keep
  working the same way, just now sitting behind the `zerov4` filter.

Using it on a closed-circuit / internal network (not exposed to the
internet, trusted user base) without `zerov4`, on its own, is also a
reasonable option; the `zerov4` recommendation specifically applies to
internet-facing or sensitive-data deployments.

## Storage

Default: JSON file based (`data_dir/users.json`, `data_dir/messages.json`).
If `sqlite` is chosen in the setup wizard, `brsxmail.db` is used in the
same data folder instead. Both backends implement the same interface,
so the endpoints work without knowing which backend was chosen.

## Notes

- This package is a closed-circuit / internal system; it does not use
  a real SMTP/email protocol and does not send mail externally.
- Domain checking defaults to `@example.com`, changeable in the setup
  wizard.
- Passwords are hashed with unsalted `sha256`, sessions are kept in
  RAM. See the "Security note" and "Using it together with zerov4"
  sections above.
