Skip to content

Memo engagement + LinkedIn sign-in (via omniauth-linkedin-openid) - #74

Merged
xrendan merged 15 commits into
mainfrom
feature/memo-engagement-users
Jul 21, 2026
Merged

Memo engagement + LinkedIn sign-in (via omniauth-linkedin-openid)#74
xrendan merged 15 commits into
mainfrom
feature/memo-engagement-users

Conversation

@xrendan

@xrendan xrendan commented Jul 21, 2026

Copy link
Copy Markdown
Member

Summary

Backend for reader memo engagement (endorse / critique) plus "Sign In with LinkedIn" so TradingPost readers can self-register and engage. Pairs with the TradingPost PR BuildCanada/TradingPost#37.

The engagement pipeline (Engagement/Endorsement/Critique models, /api/v1/memos/:slug/endorsements|critiques, /api/v1/me, seeds, local Docker rig) is the work from #38, rebased onto current main.

On top of that, this branch replaces the hand-rolled LinkedIn OIDC with the maintained omniauth-linkedin-openid strategy.

Why the auth rewrite

The original LinkedinOidc service + Users::LinkedinController hand-rolled the code exchange, JWKS id_token verification, and CSRF/redirect dance. Live end-to-end testing against real LinkedIn surfaced two bugs baked into that approach:

  1. Wrong issuer — the id_token issuer is https://www.linkedin.com/oauth, not https://www.linkedin.com. Verification always failed.
  2. Nonce always mismatched — LinkedIn does not return the nonce claim in the id_token, so payload["nonce"] == nonce was always nil == "…".

Rather than patch the bespoke code, this switches to omniauth-linkedin-openid (~360k downloads), on the same OmniAuth + Devise path already used for Google. It pins LinkedIn's OIDC endpoints and absorbs both quirks, so the bugs disappear by construction and ~165 lines of hand-rolled code are deleted.

Changes (auth commit)

  • Add omniauth-linkedin-openid; register :linkedin in devise.rb
  • User.from_linkedin maps the OmniAuth auth hash (name from extra.raw_info, avatar from info.picture_url)
  • New Users::OmniauthCallbacksController preserves the return_to + admin/profile redirect logic the Doorkeeper flow depends on
  • Sign-in button POSTs to the omniauth authorize path (CSRF protection)
  • Delete LinkedinOidc and Users::LinkedinController
  • Tests rewritten to OmniAuth test mode

Testing

  • Full suite green (582 runs, 0 failures); RuboCop clean
  • Verified live end-to-end: LinkedIn sign-in → Devise session → Doorkeeper /oauth/authorize issues a code to TradingPost → token exchange → /api/v1/me → endorse a memo writes a real Endorsement row

Note

This branch supersedes #38 — it contains #38's commits (rebased) plus the auth rewrite. Suggest closing #38 in favour of this once reviewed.

jnmclarty and others added 13 commits June 26, 2026 00:04
…n tickets

Reworks the memo engagement feature so endorsements/critiques belong to a real
authenticated User (resolved from a Doorkeeper access token), replacing the
anonymous 15-min LinkedIn 'verification ticket' flow.

- Sign in with LinkedIn (browser OIDC -> Devise session) via Users::LinkedinController,
  reusing the LinkedinOidc service; feeds the Doorkeeper authorize flow.
- engagements re-keyed to user_id (drops inline identity columns); unique
  [memo, type, user]. Identity/postal read through the User.
- Endorse/critique endpoints require a Doorkeeper token and 422 when the user
  has no postal code; /me exposes postal_code + engagement_ready and PATCH /me
  sets it.
- User: from_linkedin upsert; postal_code no longer required at account creation
  (collected at signup/first engagement), valid Canadian format enforced.
- Removes VerificationTicket, the api/v1/auth/linkedin popup controller/view, and
  the LinkedinVerified concern.
- Tests: user, engagement model, endorse/critique API, /me, LinkedIn sign-in.
The bespoke LinkedinOidc service + Users::LinkedinController hand-rolled the
OAuth code exchange, JWKS id_token verification, and CSRF/redirect dance.
Live testing surfaced two bugs baked into that approach: the id_token issuer
is https://www.linkedin.com/oauth (the code expected https://www.linkedin.com),
and LinkedIn never returns the nonce claim, so verification always failed.

Switch to omniauth-linkedin-openid — the maintained strategy, on the same
OmniAuth + Devise path already used for Google. It pins LinkedIn's OIDC
endpoints and absorbs the issuer/nonce quirks, so both bugs disappear by
construction and ~165 lines of bespoke code go away.

- Add omniauth-linkedin-openid; register :linkedin in devise.rb
- User.from_linkedin maps the OmniAuth auth hash (name from extra.raw_info,
  avatar from info.picture_url)
- New Users::OmniauthCallbacksController preserves the return_to and
  admin/profile redirect logic for the Doorkeeper flow
- Sign-in button POSTs to the omniauth authorize path (CSRF protection)
- Delete LinkedinOidc and Users::LinkedinController
- Rewrite tests to use OmniAuth test mode
xrendan added 2 commits July 20, 2026 20:02
…nt-users

# Conflicts:
#	db/seeds/trade_barriers_jurisdictions.rb
- Delete db/structure-dev.sql — orphan pg_dump copy; Rails only reads
  db/structure.sql and nothing references it (would silently drift).
- Delete Dockerfile.dev — no docker-compose file exists to consume it.
- Drop the RAILS_DEVELOPMENT_HOSTS hosts hook (part of the same absent
  docker-compose setup).
- Guard the dev-only memo_engagements sample seed behind Rails.env.development?
  so db:seed can't create a fake memo + fake users in production.
- Drop the redundant standalone index_engagements_on_memo_id — both composite
  indexes already lead with memo_id, so it only added write overhead.
@xrendan
xrendan merged commit dfe063f into main Jul 21, 2026
3 checks passed
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.

2 participants