From eaa0e3aa33e99f0748770d4b783ab5d4a4910128 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Mon, 16 Jun 2025 09:08:56 +0700 Subject: [PATCH] feat: fix github auth flow --- apps/web/package.json | 1 + .../src/app/auth/[provider]/success/page.tsx | 60 ++---- apps/web/src/app/login/page.tsx | 184 +++++++++++------- pnpm-lock.yaml | 25 +++ 4 files changed, 158 insertions(+), 112 deletions(-) diff --git a/apps/web/package.json b/apps/web/package.json index f339092f..859dacd0 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -26,6 +26,7 @@ "@agentic/platform-types": "workspace:*", "@agentic/platform-validators": "workspace:*", "@radix-ui/react-dropdown-menu": "^2.1.15", + "@radix-ui/react-label": "^2.1.7", "@radix-ui/react-slot": "^1.2.3", "@radix-ui/react-tooltip": "^1.2.7", "@tanstack/react-form": "^1.12.3", diff --git a/apps/web/src/app/auth/[provider]/success/page.tsx b/apps/web/src/app/auth/[provider]/success/page.tsx index 0408318c..b5c14e2b 100644 --- a/apps/web/src/app/auth/[provider]/success/page.tsx +++ b/apps/web/src/app/auth/[provider]/success/page.tsx @@ -1,24 +1,10 @@ 'use client' -import type { AuthorizeResult } from '@agentic/platform-api-client' import { assert } from '@agentic/platform-core' -import { useSearchParams } from 'next/navigation' +import { redirect, RedirectType, useSearchParams } from 'next/navigation' import { useEffect } from 'react' -import { useLocalStorage } from 'react-use' -import { useAgentic } from '@/components/agentic-provider' - -// function FieldInfo({ field }: { field: AnyFieldApi }) { -// return ( -// <> -// {field.state.meta.isTouched && !field.state.meta.isValid ? ( -// {field.state.meta.errors.join(',')} -// ) : null} - -// {field.state.meta.isValidating ? 'Validating...' : null} -// -// ) -// } +import { useUnauthenticatedAgentic } from '@/components/agentic-provider' export default async function Page({ params @@ -31,13 +17,16 @@ export default async function Page({ return } -function SuccessPage({ provider }: { provider: string }) { +function SuccessPage({ + provider: + // TODO + _provider +}: { + provider: string +}) { const searchParams = useSearchParams() const code = searchParams.get('code') - const { api } = useAgentic() - const [authResult] = useLocalStorage( - 'auth-result' - ) + const ctx = useUnauthenticatedAgentic() useEffect(() => { ;(async function () { @@ -46,31 +35,16 @@ function SuccessPage({ provider }: { provider: string }) { throw new Error('Missing code or challenge') } - if (!authResult) { - // TODO - throw new Error('Missing auth-result') - } - - if (!authResult.challenge) { - // TODO - throw new Error('Missing challenge') - } - - const authUser = await api.exchangeAuthCode({ - code, - redirectUri: new URL( - `/auth/${provider}/success`, - globalThis.window.location.origin - ).toString(), - verifier: authResult.challenge?.verifier + // TODO: make generic using `provider` + const authSession = await ctx!.api.exchangeOAuthCodeWithGitHub({ + code }) - console.log('AUTH SUCCESS', { - authUser, - authTokens: api.authTokens - }) + console.log('AUTH SUCCESS', { authSession }) + + redirect('/app', RedirectType.replace) })() - }, [code, api, authResult, provider]) + }, [code, ctx]) // TODO: show a loading state return null diff --git a/apps/web/src/app/login/page.tsx b/apps/web/src/app/login/page.tsx index c1898af5..04ad9e7c 100644 --- a/apps/web/src/app/login/page.tsx +++ b/apps/web/src/app/login/page.tsx @@ -4,11 +4,14 @@ import type { PasswordLoginError } from '@agentic/openauth/provider/password' import { isValidEmail, isValidPassword } from '@agentic/platform-validators' import { useForm } from '@tanstack/react-form' import { redirect, RedirectType } from 'next/navigation' -import { useState } from 'react' +import { useCallback, useState } from 'react' import { z } from 'zod' import { useUnauthenticatedAgentic } from '@/components/agentic-provider' -import { authCopy } from '@/lib/auth-copy' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { cn } from '@/lib/utils' // function FieldInfo({ field }: { field: AnyFieldApi }) { // return ( @@ -57,6 +60,14 @@ export default function LoginPage() { } }) + const onAuthWithGitHub = useCallback(async () => { + const url = await ctx!.api.initAuthFlowWithGitHub({ + redirectUri: `${globalThis.location.origin}/auth/github/success` + }) + + redirect(url, RedirectType.push) + }, [ctx]) + // TODO: if (!ctx) return null @@ -67,85 +78,120 @@ export default function LoginPage() { Log In -
{ - e.preventDefault() - void form.handleSubmit() - }} - > - {/* */} +
+
+ { + e.preventDefault() + void form.handleSubmit() + }} + > +
+

Login to your account

+

+ Enter your email below to login to your account +

+
- ( - <> - {/* */} +
+ ( +
+ - field.handleChange(e.target.value)} + + field.handleChange(e.target.value) + } + /> +
+ )} /> - {/* */} - - )} - /> + ( +
+
+ - ( - <> - {/* */} + + Forgot your password? + - field.handleChange(e.target.value)} + field.handleChange(e.target.value)} + /> +
+
+ )} /> - {/* */} - - )} - /> + [state.canSubmit, state.isSubmitting]} + children={([canSubmit, isSubmitting]) => ( + + )} + /> - [state.canSubmit, state.isSubmitting]} - children={([canSubmit, isSubmitting]) => ( - - )} - /> +
+ + Or continue with + +
-
- - {authCopy.register_prompt}{' '} - - {authCopy.register} - - + +
- - {authCopy.change_prompt} - +
+ Don't have an account?{' '} + + Sign up + +
+
- +
) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b464a021..6a619ad7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -524,6 +524,9 @@ importers: '@radix-ui/react-dropdown-menu': specifier: ^2.1.15 version: 2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + '@radix-ui/react-label': + specifier: ^2.1.7 + version: 2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) '@radix-ui/react-slot': specifier: ^1.2.3 version: 1.2.3(@types/react@19.1.8)(react@19.1.0) @@ -2611,6 +2614,19 @@ packages: '@types/react': optional: true + '@radix-ui/react-label@2.1.7': + resolution: {integrity: sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-menu@2.1.15': resolution: {integrity: sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew==} peerDependencies: @@ -8395,6 +8411,15 @@ snapshots: optionalDependencies: '@types/react': 19.1.8 + '@radix-ui/react-label@2.1.7(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0) + react: 19.1.0 + react-dom: 19.1.0(react@19.1.0) + optionalDependencies: + '@types/react': 19.1.8 + '@types/react-dom': 19.1.6(@types/react@19.1.8) + '@radix-ui/react-menu@2.1.15(@types/react-dom@19.1.6(@types/react@19.1.8))(@types/react@19.1.8)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)': dependencies: '@radix-ui/primitive': 1.1.2