Commit graph

40 commits

Author SHA1 Message Date
Khalim Conn-Kowlessar
d70b15e705 Unify invitation flow: explicit accept/decline via profile-menu notifications
Replaces the auto-accept-on-signin behaviour with an explicit accept or
decline step. Every invitee (existing user or brand new) is now treated
the same way: an invitation lands in portfolio_invitations, the email
is a deep-link "Sign in to Ara", and the invitee accepts (or declines)
explicitly from a notifications panel hanging off their profile avatar.

Why: the previous flow silently added existing users to portfolios with
no way to refuse, and gave them no in-app confirmation that the invite
landed. Existing users complained they didn't know which account they
were signed in as either — both gaps are closed by the same panel.

Backend:
  - POST /collaborators no longer creates portfolioUsers directly for
    existing users; it writes a portfolio_invitations row in every case
    except the trivial "already a member of THIS portfolio" path
    (silent role update, no email)
  - New /api/user/invitations endpoint: GET lists pending invitations
    addressed to the current user across all portfolios, joined with
    portfolio + inviter context; POST accepts or declines a single
    invitation, scoped to session.email so users can't act on others'
  - Accept reuses the existing planInvitationApplication helper for
    the "already a member" idempotency check
  - Decline is a silent delete (matches GitHub/Linear/Notion convention;
    no email to inviter, no tombstone)
  - signIn callback no longer auto-applies pending invitations — that
    block is removed entirely along with its now-unused imports
  - Email template subject + body unified, no longer suggests the user
    is "added"; both modes say "invited" and direct them to the profile
    menu

Frontend:
  - ProfileDropDown rewritten as a notifications panel: shows the
    signed-in email at the top (closing the "which account?" gap),
    lists pending invitations with Accept/Decline buttons, displays a
    red count badge on the avatar (max "9+"). Uses TanStack Query with
    optimistic update on accept/decline and toast on outcome. Existing
    Help + Sign Out menu items preserved.
  - No useEffect — pending-count derived from query data, mutations
    handle the rest

Vercel preview test plan:
  - Invite a user already in another portfolio → red badge appears on
    their next page load; Accept lands them in the portfolio
  - Invite a new email → sign-up flow finishes; new account lands on
    home with a badge waiting for Accept (no longer auto-accepted)
  - Existing member of THIS portfolio re-invited → silent role update,
    no email

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-28 09:43:13 +00:00
Khalim Conn-Kowlessar
c921db7d9c initial implementation for portfolio invitations. A user can send an invitation to a user and they will receive an invitation email 2026-05-27 16:18:21 +00:00
Khalim Conn-Kowlessar
d042606955 Add 6-digit code sign-in as primary, magic link as fast-path fallback
Same email now contains a 6-digit code and a magic link, both backed by a
single verificationToken row. After submitting their email, the user
lands on /auth/verify-code with a single-input form (inputmode=numeric,
autocomplete=one-time-code, auto-submit on 6 digits or paste) and can
either type the code or use the link from the email. Either path
consumes the same row — single-use, replace-on-resend.

This is the structural fix for the silent-quarantine pattern observed
with Atkins and Sustainable Building UK: corporate gateways are happier
with short transactional content than long opaque token URLs, and a
code can't be broken by SafeLinks-style URL rewriting. The link path is
preserved so users whose email gets through unmangled keep one-click UX.

Security:
  - Codes are 6-digit, crypto.randomInt-generated, stored as sha256
    hashed against NEXTAUTH_SECRET on the same row as the link token
  - 5-attempt lockout per code (attempts column); 6th attempt with the
    correct code still fails
  - Per-email send rate limit: 5/hour fixed window (authRateLimits
    table); 6th send returns an error
  - Code + link share a 10-minute window (maxAge dropped from 1h)
  - Resending replaces any prior token rows for the identifier so only
    the latest send is ever live

Implementation:
  - verificationCode.ts holds generateCode + hashCode + the pure
    evaluateCodeAttempt decision tree; 9 unit tests cover every branch
    of the verification outcome (no-such-row, expired, locked-out, ok,
    wrong-with-newAttempts, locked-out-still-rejects-correct-code)
  - sendVerificationRequest now hashes the URL token the same way
    /verify/[token]/page.tsx does, applies the rate limit + records the
    code + replaces older rows in two transactions
  - CredentialsProvider (id: "email-code") calls evaluateCodeAttempt
    inside a transaction, handles all 5 outcomes, creates the user on
    first successful code (parity with the magic-link callback path)
  - oauthId backfill in the signIn callback is now guarded on
    account.type === "oauth" so the credentials flow doesn't pollute
    oauthProvider with "email-code"
  - Migration is additive: code_hash nullable, attempts default 0; new
    authRateLimits table is independent. In-flight tokens at deploy time
    keep working via the link path.

Vercel preview deployment is the test surface; a Mailpit + Cypress E2E
loop is intentionally deferred per the lean-setup plan in docs/wip/
auth-email-code-fallback-plan.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:16:47 +00:00
Khalim Conn-Kowlessar
9c569f5584 Add SES observability foundation for email auth (PR 1 of code-fallback)
Wires the X-SES-CONFIGURATION-SET header on outbound auth emails so SES
bounce/delivery events flow through dev-ses-config to the dev-ses-events
SNS topic. Replaces the fire-and-forget "EMAIL MAGIC LINK SENT" log
(which fired before the SMTP transaction and swallowed downstream errors)
with structured EMAIL_MAGIC_LINK_SUCCESS/_FAILURE logs carrying the
Nodemailer messageId, so app-side sends are now correlatable with SES
events.

Motivated by the Atkins / Sustainable Building UK silent-quarantine
incidents where we couldn't tell whether SES had even tried to send.

Plan doc at docs/wip/auth-email-code-fallback-plan.md tracks the
broader email-code-fallback design that PR 2 will implement.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 13:15:25 +00:00
Khalim Conn-Kowlessar
a0bfeae742 changing magic links url to protect against microsoft defender and host domna logo 2026-03-10 14:59:35 +00:00
Khalim Conn-Kowlessar
fc8373f9ea fixed scenario re-creationg every time bug 2025-11-25 11:06:34 +00:00
Khalim Conn-Kowlessar
6c0629c78e enhanced logging and added verification error catching 2025-11-13 22:30:11 +00:00
Khalim Conn-Kowlessar
8ca14c39ad Fixing auth options export issue 2025-10-28 11:49:19 +00:00
Khalim Conn-Kowlessar
a9c4aa4ec4 minor 2025-10-17 19:19:21 +00:00
Khalim Conn-Kowlessar
4ae02f5af7 fixed oauth login bug 2025-10-17 19:18:19 +00:00
Khalim Conn-Kowlessar
ea8dc9f5be fixed user id assignment bug 2025-10-17 15:57:00 +00:00
Khalim Conn-Kowlessar
73efe32801 magic link email tempalte live 2025-10-17 13:55:46 +00:00
Khalim Conn-Kowlessar
a264686552 onboarding working 2025-10-15 19:07:10 +00:00
Khalim Conn-Kowlessar
4014791306 onboarding logic working 2025-10-15 14:52:39 +00:00
Khalim Conn-Kowlessar
2d4a11b94d login working 2025-10-13 18:49:06 +00:00
Khalim Conn-Kowlessar
7d51fcc4dc working on sign in and onboarding 2025-10-13 08:52:34 +00:00
Khalim Conn-Kowlessar
fdada1af06 Added manaual email login 2024-09-10 18:44:03 +01:00
Khalim Conn-Kowlessar
31a1bf77a8 final test 2024-09-10 17:35:53 +01:00
Khalim Conn-Kowlessar
10952d2874 redeploy 2024-09-10 17:27:59 +01:00
Khalim Conn-Kowlessar
393f9c602a temp 2024-09-10 17:21:33 +01:00
Khalim Conn-Kowlessar
345cfc670a temp test 2024-09-10 17:16:17 +01:00
Khalim Conn-Kowlessar
1d0fab8ec5 force login 2024-09-10 16:47:25 +01:00
Khalim Conn-Kowlessar
0e3c025f4f maybe got ms integration working 2024-09-10 16:32:11 +01:00
Khalim Conn-Kowlessar
6da2fd1748 removed hard coding 2024-09-10 04:30:04 +01:00
Khalim Conn-Kowlessar
69da8bc3db test 2024-09-10 04:05:47 +01:00
Khalim Conn-Kowlessar
07e1ae9986 test 2024-09-10 04:01:31 +01:00
Khalim Conn-Kowlessar
1f5d92f081 Getting vercel url 2024-09-10 03:55:45 +01:00
Khalim Conn-Kowlessar
4559df9e8b ms login working 2024-09-10 03:40:10 +01:00
Khalim Conn-Kowlessar
8b3a9ca74e Attempting to serve static file 2024-09-09 22:54:55 +01:00
Khalim Conn-Kowlessar
2bb9bda323 Added microsoft sign in 2024-04-15 11:54:10 +01:00
Khalim Conn-Kowlessar
3e2905a6dd fix userid serialization due to bigint format bug 2023-08-02 10:41:18 +01:00
Khalim Conn-Kowlessar
1e305912f6 Set up persistence of user id in session on client and server and pass userid to db call 2023-07-11 22:48:03 +01:00
Khalim Conn-Kowlessar
d864afb244 Fixed silly sign in bug 2023-07-11 10:43:39 +01:00
Khalim Conn-Kowlessar
64f5649db1 Added better error handling for sign in api 2023-07-11 10:31:26 +01:00
Khalim Conn-Kowlessar
d1e9d8949d implemented user sign in against database 2023-07-11 10:24:46 +01:00
Khalim Conn-Kowlessar
50ee1db7a0 Allow any user to log into demo 2023-06-05 17:55:06 +01:00
Khalim Conn-Kowlessar
e8025f85f2 Added next auth middleware and got re-routing logic that I'm happy with 2023-05-25 15:29:54 +01:00
Khalim Conn-Kowlessar
69ae460536 Working on sign in 2023-05-25 12:27:32 +01:00
Khalim Conn-Kowlessar
36dea89c8f implemented auth but need to fix callback page 2023-05-25 11:52:52 +01:00
Khalim Conn-Kowlessar
c3346b9583 Added api and components folders and installed headlessui 2023-05-25 09:49:48 +01:00