feat(mcp-oauth): let an account with no company connect and sign up from the OAuth popup (#1814 PR 1) (#1855)
* feat(mcp-oauth): let an account with no company connect and sign up from the OAuth popup Identity unlock for agent-first onboarding (#1814, shape B+). A person with no Accounted account can now connect from an MCP client, create the account inside the Connect popup and finish the OAuth dance. - authorize/token no longer require a company: consent renders a companyless variant and the key is minted with company_id NULL. - validateApiKey returns companyId string|null and binds an unbound key to the user's first company on the first validation after it exists. - MCP server: company-dependent tools and data resources answer with a structured NO_COMPANY_YET error; the company-independent tools still run; telemetry skips when there is no company scope. - /api/events fails closed instead of throwing for an unbound key. - authorize forces TOTP enrollment (not just verification) for password accounts with no factor, since the middleware skips enrollment for zero-company users; BankID-linked accounts stay exempt. - /login forwards next to /register; register, GoogleAuthButton and /auth/callback carry it back to the consent page (callback honours only /api/mcp-oauth/authorize, via safeReturnTo); /mfa/enroll hard-navigates to /api/* destinations like /mfa/verify. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018wCdzRTatKiDByKB8hCNT6 * refactor(company): move getActiveCompanyId out of the next/headers module lib/auth/api-keys.ts needs the resolver for unbound-key binding, but lib/company/context.ts imports next/headers for the legacy company cookie and Turbopack refuses that import on some of api-keys' import paths (the preview build failed). The resolver and CompanyContextError now live in lib/company/active-company.ts; context.ts re-exports them so every caller and test mock is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018wCdzRTatKiDByKB8hCNT6 * fix(mcp-oauth): fail closed on a failed assurance lookup; enroll Back aborts instead of looping Review findings on #1855: requireAal2 let consent through at AAL1 when getAuthenticatorAssuranceLevel() returned nothing and a verified factor existed. Only a positive AAL2 answer passes now; a failed lookup and the inconsistent verified-factor-at-AAL1 case both step up to /mfa/verify. Back on /mfa/enroll with the consent page as returnTo went straight back into the redirect loop; it now aborts to the app. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018wCdzRTatKiDByKB8hCNT6 --------- Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
Jakob Wennberg
parent
2a33291c18
commit
a717f03898
@@ -21,6 +21,7 @@ import { GoogleMark } from '@/components/ui/provider-marks'
|
||||
export function GoogleAuthButton({
|
||||
onError,
|
||||
compact = false,
|
||||
next,
|
||||
}: {
|
||||
onError: (message: string) => void
|
||||
/**
|
||||
@@ -29,6 +30,14 @@ export function GoogleAuthButton({
|
||||
* kept as the accessible name.
|
||||
*/
|
||||
compact?: boolean
|
||||
/**
|
||||
* Post-auth destination, already passed through safeReturnTo by the caller.
|
||||
* Forwarded to /auth/callback as `next` so an OAuth sign-in or sign-up that
|
||||
* started from the MCP consent page (/login?next=/api/mcp-oauth/authorize…)
|
||||
* resumes the consent flow instead of landing on the dashboard. '/' (the
|
||||
* safeReturnTo fallback) means no destination and is not forwarded.
|
||||
*/
|
||||
next?: string
|
||||
}) {
|
||||
const [isRedirecting, setIsRedirecting] = useState(false)
|
||||
const supabase = createClient()
|
||||
@@ -38,10 +47,13 @@ export function GoogleAuthButton({
|
||||
const handleClick = async () => {
|
||||
setIsRedirecting(true)
|
||||
try {
|
||||
const callback = new URL('/auth/callback', window.location.origin)
|
||||
callback.searchParams.set('flow', 'oauth')
|
||||
if (next && next !== '/') callback.searchParams.set('next', next)
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'google',
|
||||
options: {
|
||||
redirectTo: `${window.location.origin}/auth/callback?flow=oauth`,
|
||||
redirectTo: callback.toString(),
|
||||
},
|
||||
})
|
||||
if (error) {
|
||||
|
||||
Reference in New Issue
Block a user