Reusable OAuth2 authentication against GoPaperless (Nextcloud/LibreSign) — authorize, callback, and auto-refreshing bearer tokens keyed on an opaque subject.
Project description
gopaperless-auth (Python)
Reusable OAuth2 authentication against GoPaperless. Drop it into any backend so your users log in with their GoPaperless account and your app gets an auto-refreshing bearer token for them — no passwords handled, no token lifecycle to reinvent.
pip install gopaperless-auth
Why
Your users own GoPaperless accounts. This library runs the OAuth2 authorization-code flow
on their behalf, fetches their GoPaperless identity (account id, email, display name), and
stores encrypted tokens keyed on a subject you choose (a user id, a phone number —
anything). Later, get_valid_token(subject) hands you a token that's valid right now,
refreshing transparently when needed.
60-second quick start
from gopaperless_auth import GoPaperlessAuth
auth = GoPaperlessAuth(
base_url="https://gopaperless.ke",
client_id="my-app-client-id", # from your GoPaperless admin (OAuth clients)
client_secret="…",
redirect_uri="https://my-app.com/auth/callback",
encryption_key="<fernet-key>", # Fernet.generate_key().decode()
# store=None -> in-memory (dev). See "Storage tiers" for production.
)
# 1. Send the user to GoPaperless to log in
url = auth.authorize_url(subject="user-123", extra={"return_to": "/documents"})
# -> redirect the user's browser to `url`
# 2. GoPaperless redirects back to your redirect_uri with ?code=…&state=…
result = await auth.handle_callback(code, state)
# result.subject == "user-123"; result.identity.user_id / .email / .display_name
# 3. Anywhere later, get a valid bearer token (auto-refreshes)
token = await auth.get_valid_token("user-123")
# use it: Authorization: Bearer {token} against the GoPaperless API
Generate the encryption key once and keep it secret (env var / secrets manager):
from cryptography.fernet import Fernet
print(Fernet.generate_key().decode())
Storage tiers
You never have to run Redis. Tokens just need to live somewhere; pick the tier that fits.
| Tier | How | When |
|---|---|---|
| 0 — in-memory (default) | store=None |
dev / trying it out (lost on restart) |
| 1 — database URL | store="postgresql+asyncpg://…" or "sqlite+aiosqlite:///tokens.db" |
production; the SDK creates & manages its own gopaperless_oauth_tokens table (needs pip install "gopaperless-auth[sql]") |
| 2 — your own store | store=MyStore() |
persist inside your existing user DB |
| 3 — stateless | use OAuthClient directly |
"just give me the token, I'll handle storage" |
Tier 2 — bring your own storage (implement three async methods):
from gopaperless_auth import TokenStore, TokenRecord
class MyStore(TokenStore):
async def get(self, subject: str) -> TokenRecord | None: ...
async def save(self, record: TokenRecord) -> None: ...
async def delete(self, subject: str) -> None: ...
auth = GoPaperlessAuth(..., store=MyStore())
Records handed to a store are already encrypted — your store persists opaque strings and never sees the encryption key.
Tier 3 — fully stateless (no store, no lifecycle opinions):
from gopaperless_auth.client import OAuthClient
from gopaperless_auth import OAuthConfig
client = OAuthClient(OAuthConfig(base_url=…, client_id=…, client_secret=…,
redirect_uri=…, encryption_key=…))
tokens = await client.exchange_code(code) # raw access/refresh, stored nowhere
identity = await client.fetch_identity(tokens.access_token)
fresh = await client.refresh(tokens.refresh_token)
FastAPI example
from fastapi import FastAPI, Request
from fastapi.responses import RedirectResponse
app = FastAPI()
auth = GoPaperlessAuth(..., store="postgresql+asyncpg://…")
@app.get("/login")
async def login(user_id: str):
return RedirectResponse(auth.authorize_url(subject=user_id))
@app.get("/auth/callback") # must match the registered redirect_uri
async def callback(code: str, state: str):
result = await auth.handle_callback(code, state)
return {"linked": result.identity.email}
@app.get("/sign")
async def sign(user_id: str):
token = await auth.get_valid_token(user_id) # raises TokenExpiredError if re-auth needed
# call GoPaperless with Authorization: Bearer {token}
API reference
GoPaperlessAuth(base_url, client_id, client_secret, redirect_uri, encryption_key, store=None, token_expiry_margin=120)
authorize_url(subject, extra=None) -> str— URL to send the user to.stateis HMAC-signed so a callback can't be spoofed with an arbitrary subject.await handle_callback(code, state) -> AuthResult— exchanges the code, fetches identity, persists encrypted tokens. ReturnsAuthResult(subject, identity, tokens, extra).await get_valid_token(subject) -> str— valid bearer token, auto-refreshed. RaisesTokenNotFoundError(never authed) orTokenExpiredError(refresh dead → send them throughauthorize_urlagain).await get_identity(subject) -> Identity | Noneawait logout(subject)— delete stored tokens.
Errors
GoPaperlessAuthError (base) · ConfigError · CodeExchangeError · TokenExpiredError
· TokenNotFoundError · StateError.
Provider setup (one-time, per app)
In your GoPaperless admin, register an OAuth client for your app. Set the redirect URI to your app's callback (it's validated exactly). Copy the generated Client Identifier and Secret into your config. Register one client per app so each can be rotated/revoked independently.
Note — no scopes. GoPaperless issues full-account tokens; there is no "signing-only" scope. Treat the token as full access to that user's GoPaperless account.
License
MIT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gopaperless_auth-0.1.0.tar.gz.
File metadata
- Download URL: gopaperless_auth-0.1.0.tar.gz
- Upload date:
- Size: 12.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d0401337eb1d92c26951e7a797393e234a7a393f895e596d9f3060a87efc9c05
|
|
| MD5 |
9a54e363e7a9ea8c63e91ee2395bb396
|
|
| BLAKE2b-256 |
c3b2fc00cad9dc00a54bdc82c76ce1a64fc2154260a8ef63263a3339ed9066b7
|
File details
Details for the file gopaperless_auth-0.1.0-py3-none-any.whl.
File metadata
- Download URL: gopaperless_auth-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b43cebbee9767bc1176ab57dccd626995f41a727062eefed06cfd68d09c3f670
|
|
| MD5 |
be5041c06d048ba650e16ba75fc3355f
|
|
| BLAKE2b-256 |
a57fe464df23a6b5dd78bdecc255ca35fa54b78276ea2450ba6e963df2379289
|