Skip to main content

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. state is 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. Returns AuthResult(subject, identity, tokens, extra).
  • await get_valid_token(subject) -> str — valid bearer token, auto-refreshed. Raises TokenNotFoundError (never authed) or TokenExpiredError (refresh dead → send them through authorize_url again).
  • await get_identity(subject) -> Identity | None
  • await 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gopaperless_auth-0.1.0.tar.gz (12.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gopaperless_auth-0.1.0-py3-none-any.whl (15.6 kB view details)

Uploaded Python 3

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

Hashes for gopaperless_auth-0.1.0.tar.gz
Algorithm Hash digest
SHA256 d0401337eb1d92c26951e7a797393e234a7a393f895e596d9f3060a87efc9c05
MD5 9a54e363e7a9ea8c63e91ee2395bb396
BLAKE2b-256 c3b2fc00cad9dc00a54bdc82c76ce1a64fc2154260a8ef63263a3339ed9066b7

See more details on using hashes here.

File details

Details for the file gopaperless_auth-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for gopaperless_auth-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 b43cebbee9767bc1176ab57dccd626995f41a727062eefed06cfd68d09c3f670
MD5 be5041c06d048ba650e16ba75fc3355f
BLAKE2b-256 a57fe464df23a6b5dd78bdecc255ca35fa54b78276ea2450ba6e963df2379289

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page