Skip to content

feat(auth): generic OAuth/OIDC logins (Google, GitHub, Facebook, Apple)#171

Merged
v0l merged 4 commits into
masterfrom
feat/oauth-logins
Jul 17, 2026
Merged

feat(auth): generic OAuth/OIDC logins (Google, GitHub, Facebook, Apple)#171
v0l merged 4 commits into
masterfrom
feat/oauth-logins

Conversation

@v0l

@v0l v0l commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Fixes #167

Adds generic external OAuth/OIDC login alongside the existing NIP-98 Nostr auth. The pubkey is treated as an opaque account identity, so almost all existing code is unchanged.

Approach

  • Identity model — new account_type on users (nostr | oauth). OAuth accounts store a synthetic sha256("{provider}:{subject}") in the existing 32-byte pubkey column (no schema-type change; unchanged for existing rows, all nostr).
  • Dual-scheme auth — the Nip98Auth extractor now also accepts Authorization: Bearer <session-jwt> in addition to Authorization: Nostr <event>. Handlers use auth.pubkey(), so the ~40 existing call sites were a mechanical rename.
  • Session tokens — stateless HS256 JWTs (via hmac/sha2, already in the lockfile) issued after login, plus a signed CSRF state helper. Secret comes from config.

Providers

GET /api/v1/oauth/{provider}/login → provider redirect; GET/POST /api/v1/oauth/{provider}/callback → code exchange → user upsert → session JWT (redirect with #token= or JSON). Configured under a new oauth section, each with type: google | github | facebook | apple | oidc.

Per-provider quirks handled:

  • GitHub — required User-Agent, numeric id subject, primary verified email from /user/emails
  • Facebook — Graph me endpoint
  • Apple — no userinfo (subject from id_token), dynamically-signed ES256 client-secret JWT (p256), and form_post (POST) callback

Email sync

On first login the provider email is synced into the account (marked verified when the provider asserts it) and contact_email enabled by default (OAuth users have no NIP-17 channel). Non-destructive (never overwrites later user edits) and best-effort (a sync failure never blocks login).

Nostr-only features gated to native accounts

NIP-17 DMs, npub display (VM-created notification, invoice), and LIR agreement signing now check account_type == Nostr.

Tests

  • Session JWT issue/verify/tamper/expiry + CSRF state round-trip
  • oauth_pubkey determinism + upsert_oauth_user (type + idempotency)
  • Apple ES256 client-secret JWT (sign then verify against the P-256 key)
  • id_token claim extraction, string/bool email_verified normalization
  • Provider config defaults/quirks for all five types
  • First-login email sync (set + verified + contactable, no clobber on relogin)

Migration

20260718003000_user_account_type.sqlaccount_type smallint unsigned not null default 0.

Notes

  • Apple's id_token subject/email are read without re-verifying the signature, since the token arrives over TLS directly from Apple's token endpoint (back-channel trust). JWKS verification is an easy follow-up if desired.
  • New dep: p256 (features ecdsa, pkcs8, pem) on lnvps_api — all supporting crates already in Cargo.lock.

v0l added 4 commits July 17, 2026 13:25
Add external OAuth/OIDC login alongside the existing NIP-98 Nostr auth.

- New account_type on users (nostr|oauth); OAuth accounts use a synthetic
  sha256("{provider}:{subject}") identity in place of a Nostr pubkey.
- Nip98Auth extractor now also accepts Authorization: Bearer <session-jwt>,
  issued after login; stateless HS256 tokens with a CSRF state helper.
- /api/v1/oauth/{provider}/login + GET/POST callback with per-provider
  handling: GitHub User-Agent + numeric id + /user/emails, Facebook Graph,
  Apple id_token subject, ES256 client-secret JWT and form_post callback.
- Provider email synced into the account on first login (verified where the
  provider asserts it, contact_email enabled), non-destructive + best-effort.
- Nostr-only features (NIP-17 DMs, npub display, LIR agreement signing) are
  gated to native Nostr accounts.

Fixes #167
- GET /api/v1/account now returns account_type ('nostr'|'oauth') so the
  frontend can hide Nostr-only UI (npub, NIP-17 DM settings) for OAuth users.
- PATCH /api/v1/account rejects enabling contact_nip17 for OAuth accounts,
  consistent with the other contact-channel validations.
- Remove the 'NPUB:' line from VM-created user and admin notifications.
- Replace the invoice 'Nostr Pubkey' line with the account email (shown
  only when set) - a universal identifier for both Nostr and OAuth accounts.
@v0l
v0l merged commit 7819183 into master Jul 17, 2026
8 checks passed
@v0l
v0l deleted the feat/oauth-logins branch July 17, 2026 12:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

oAuth Logins

1 participant