init: async IMAP OTP retrieval
Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
commit
c08d13c02a
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@ -0,0 +1,16 @@
|
||||
# claude
|
||||
CLAUDE.md
|
||||
|
||||
# python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
.eggs/
|
||||
|
||||
# env
|
||||
.venv/
|
||||
venv/
|
||||
.env
|
||||
.pytest_cache/
|
||||
91
README.md
Normal file
91
README.md
Normal file
@ -0,0 +1,91 @@
|
||||
# aiomail
|
||||
|
||||
Async IMAP one-time-code retrieval. Reads OTP / login codes out of IMAP mailboxes
|
||||
**you own**, with password or OAuth2 (XOAUTH2) auth and dynamic sender / subject /
|
||||
code matching.
|
||||
|
||||
This reads codes from email; it does not generate them (that is `pyotp`'s job).
|
||||
|
||||
## Install
|
||||
|
||||
`requirements.txt`:
|
||||
|
||||
```
|
||||
aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.0
|
||||
# OAuth token providers (Microsoft / Google) need the extra:
|
||||
aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.0
|
||||
```
|
||||
|
||||
Direct:
|
||||
|
||||
```bash
|
||||
pip install "aiomail @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.0"
|
||||
pip install "aiomail[oauth] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aiomail.git@v0.1.0"
|
||||
```
|
||||
|
||||
Requires `aioimaplib` and `beautifulsoup4` (pulled transitively). The `oauth`
|
||||
extra adds `aiohttp` for the refresh-token providers.
|
||||
|
||||
## Password auth
|
||||
|
||||
```python
|
||||
from aiomail import IMAPClient, PasswordAuth, retrieve_otp
|
||||
|
||||
client = IMAPClient(PasswordAuth(user, password), host="outlook.office365.com")
|
||||
async with client:
|
||||
code = await retrieve_otp(client, sender="no-reply@privy.io", subject="login code")
|
||||
```
|
||||
|
||||
## OAuth2 auth
|
||||
|
||||
Pass a static access token, or a token provider that refreshes one on connect:
|
||||
|
||||
```python
|
||||
from aiomail import IMAPClient, OAuth2Auth, retrieve_otp
|
||||
from aiomail.oauth import MicrosoftTokenProvider
|
||||
|
||||
auth = OAuth2Auth(user, token_provider=MicrosoftTokenProvider(client_id, refresh_token))
|
||||
client = IMAPClient(auth, host="outlook.office365.com")
|
||||
async with client:
|
||||
code = await retrieve_otp(client, sender="no-reply@privy.io")
|
||||
```
|
||||
|
||||
`GoogleTokenProvider(client_id, refresh_token, client_secret)` is also provided.
|
||||
Credentials are always supplied by you — nothing is hardcoded.
|
||||
|
||||
## Dynamic matching
|
||||
|
||||
`sender` and `subject` accept a substring, a compiled regex, or a callable:
|
||||
|
||||
```python
|
||||
import re
|
||||
await retrieve_otp(client, sender="uber.com") # substring
|
||||
await retrieve_otp(client, sender=re.compile(r"no-?reply@.*\.io")) # regex
|
||||
await retrieve_otp(client, sender=lambda f: f.endswith("@x.com")) # callable
|
||||
```
|
||||
|
||||
Code extraction is tunable too — `patterns` (regexes, first group wins) and
|
||||
`lengths` (standalone digit-run fallback):
|
||||
|
||||
```python
|
||||
from aiomail import extract_code
|
||||
extract_code(message, patterns=[r"PIN[:\s]+(\d{6})"], lengths=(6,))
|
||||
```
|
||||
|
||||
## Scope
|
||||
|
||||
`retrieve_otp` walks `folders` newest-first, filters by sender/subject, extracts a
|
||||
code, and applies `max_age` (seconds; `None` disables). Provider quirks (folder
|
||||
names, code lengths, freshness) are parameters, not hardcoded branches.
|
||||
|
||||
For mailboxes / accounts you own and control.
|
||||
|
||||
## Verification status
|
||||
|
||||
Pure logic (`extract_code`, `as_predicate`, `retrieve_otp`) and the IMAP entrypoints
|
||||
are verified against the installed `aioimaplib` API. The **live-server paths are not
|
||||
fully tested**: end-to-end XOAUTH2 login against real Outlook/Gmail, the
|
||||
Microsoft/Google refresh-token exchange (scopes may need adjusting to your app
|
||||
registration), and the iCloud `(BODY[])` fetch. Password IMAP and the matching logic
|
||||
work; **confirm OAuth end-to-end against your own mailbox before relying on it in
|
||||
production.**
|
||||
Loading…
Reference in New Issue
Block a user