Skip to content

Commit

Permalink
Route for native oauth to open with WebBrowser.openAuthSessionAsync
Browse files Browse the repository at this point in the history
  • Loading branch information
rmarscher committed Oct 30, 2023
1 parent 29a220f commit 837c9f1
Show file tree
Hide file tree
Showing 11 changed files with 106 additions and 78 deletions.
13 changes: 13 additions & 0 deletions apps/next/pages/oauth/[provider].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { OAuthSignInScreen } from 'app/features/oauth/screen'
import Head from 'next/head'

export default function Page() {
return (
<>
<Head>
<title>OAuth Sign In</title>
</Head>
<OAuthSignInScreen />
</>
)
}
2 changes: 2 additions & 0 deletions packages/api/.dev.vars.example
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ APPLE_KEY_ID=
APPLE_CERTIFICATE=
DISCORD_CLIENT_ID=
DISCORD_CLIENT_SECRET=
RESEND_API_KEY=
NEXT_PUBLIC_SUPPORT_EMAIL=
4 changes: 3 additions & 1 deletion packages/api/src/auth/hono.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ export const handleOAuthCallback = async (c: HonoContext, ctx: ApiContextProps,
throw new Error("Unknown auth service: " + service)
}
const storedState = getCookie(c, service + "_oauth_state");
const storedRedirectUrl = getCookie(c, service + "_oauth_redirect");
const state = c.req.query('state');
const code = c.req.query('code');
if (
Expand All @@ -109,7 +110,8 @@ export const handleOAuthCallback = async (c: HonoContext, ctx: ApiContextProps,
const user = await getOAuthUser(service, ctx, { state, code })
const session = await createSession(ctx.auth, user.userId)
ctx.authRequest?.setSession(session);
return c.redirect(ctx.env.APP_URL, 302);
const redirectTo = (storedRedirectUrl ? storedRedirectUrl : ctx.env.APP_URL) + "#token=" + session.token;
return c.redirect(redirectTo, 302);
} catch (e) {
if (e instanceof OAuthRequestError) {
// invalid code
Expand Down
1 change: 0 additions & 1 deletion packages/api/src/auth/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@ export type SignInResult = {
session?: Session | null
message?: string
codeSent?: boolean,
oauthState?: string | null,
oauthRedirect?: string,
}

Expand Down
6 changes: 3 additions & 3 deletions packages/api/src/context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,15 +64,15 @@ export const createContext = async (
const auth = createHonoAuth(env.DB, env.APP_URL, context.req)

async function getSession() {
let session: Session | undefined
let session: Session | null = null
let authRequest: AuthRequest<Lucia.Auth> | undefined

if (context.req) {
authRequest = auth.handleRequest(context)
session = await authRequest.validate() || undefined
session = await authRequest.validate()
// Allow for either cookie or bearer token
if (!session) {
session = await authRequest.validateBearerToken() || undefined
session = await authRequest.validateBearerToken()
}
}
return { session, authRequest }
Expand Down
5 changes: 4 additions & 1 deletion packages/api/src/routes/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -211,8 +211,11 @@ const signIn = async ({ ctx, input }: { ctx: ApiContextProps, input: Input<typeo
const provider = getAuthProvider(ctx, input.provider)
const [url, state] = await provider.getAuthorizationUrl()
ctx.setCookie(`${provider}_oauth_state=${state}; HttpOnly; SameSite=Strict`)
if (input.redirectTo) {
// TODO should probably validate the redirect starts with APP_URL, API_URL or t4://
ctx.setCookie(`${provider}_oauth_redirect=${input.redirectTo}; HttpOnly; SameSite=Strict`)
}
res.oauthRedirect = url.toString()
res.oauthState = state
} if (input.email) {
if (input.code) {
res = await signInWithCode(ctx, 'email', input.email, input.code, ctx.setCookie)
Expand Down
1 change: 1 addition & 0 deletions packages/api/src/schema/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export const SignInSchema = object({
email: optionalEmail,
code: optional(string()), // for password-less sign in
provider: optional(authProvidersInput),
redirectTo: optional(string()),
// Used with apple sign-in on native
token: optional(string()),
nonce: optional(string()),
Expand Down
34 changes: 34 additions & 0 deletions packages/app/features/oauth/screen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { AuthProviderName } from '@t4/api/src/auth/shared'
import { Paragraph } from '@t4/ui'
import { useSignIn } from 'app/utils/auth'
import { useEffect } from 'react'
import { createParam } from 'solito'

const { useParam } = createParam<{ provider: AuthProviderName, redirectTo: string }>()

export const OAuthSignInScreen = (): React.ReactNode => {
const { signIn } = useSignIn()
const [provider] = useParam('provider')
const [redirectTo] = useParam('redirectTo')

useEffect(() => {
if (!provider) return
if (!window) return
const exec = async () => {
const res = await signIn({ provider, redirectTo })
console.log('signIn result', res)
if (!res?.oauthRedirect) {
if (redirectTo) {
window.location.href = redirectTo
}
} else {
window.location.href = res.oauthRedirect
}
}
exec()
}, [provider])

return (
<Paragraph p="$8">Redirecting to sign in...</Paragraph>
)
}
59 changes: 22 additions & 37 deletions packages/app/features/sign-in/screen.native.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { AuthProviderName } from '@t4/api/src/auth/shared'
import { YStack, useToastController } from '@t4/ui'
import { SignUpSignInComponent } from 'app/features/sign-in/SignUpSignIn'
import { useRouter } from 'solito/router'
import type { Provider } from '@supabase/supabase-js'
import Constants from 'expo-constants'
import { capitalizeWord } from 'app/utils/string'
import { useSignIn } from 'app/utils/auth'
import { initiateAppleSignIn } from 'app/utils/auth/appleAuth'
import { storeSessionToken } from 'app/utils/auth/credentials'
import { isExpoGo } from 'app/utils/flags'
import { useSupabase } from 'app/utils/supabase/hooks/useSupabase'
import * as WebBrowser from 'expo-web-browser'
import { capitalizeWord } from 'app/utils/string'
import { trpc } from 'app/utils/trpc'
import { getInitialURL } from 'expo-linking'
import * as WebBrowser from 'expo-web-browser'
import { Platform } from 'react-native'
import { initiateAppleSignIn } from 'app/utils/auth/appleAuth'
import { useSignIn } from 'app/utils/auth'
import { AuthProviderName } from '@t4/api/src/auth/shared'

export const SignInScreen = (): React.ReactNode => {
const { push } = useRouter()
const { signIn } = useSignIn()
const toast = useToastController()
const { signIn } = useSignIn()
const utils = trpc.useUtils()

const postLogin = () => {
utils.user.invalidate()
utils.auth.invalidate()
}


const signInWithApple = async () => {
try {
const { token, nonce } = await initiateAppleSignIn()
Expand All @@ -37,39 +41,20 @@ export const SignInScreen = (): React.ReactNode => {
}

const handleOAuthWithWeb = async (provider: AuthProviderName) => {
// FIXME hmm... probably could to host a screen with nextjs to handle this

try {
const res = await signIn({ provider })
const redirectUri = (await getInitialURL()) || 't4://'
const response = await WebBrowser.openAuthSessionAsync(
`${process.env.NEXT_PUBLIC_APP_URL}/oauth?provider=${provider}&redirect_to=${redirectUri}`,
process.env.NEXT_PUBLIC_APP_URL + '/oauth/' + provider,
redirectUri
)
if (response.type === 'success') {
const url = response.url
const params = new URLSearchParams(url.split('#')[1])
const accessToken = params.get('access_token') || ''
const refreshToken = params.get('refresh_token') || ''
await supabase.auth
.setSession({
access_token: accessToken,
refresh_token: refreshToken,
})
.then(({ data: { session }, error }) => {
if (session) {
// @ts-ignore set session does not call subscribers when session is updated
supabase.auth._notifyAllSubscribers('SIGNED_IN', session)
replace('/')
} else {
if (!isExpoGo) {
toast.show(capitalizeWord(provider) + ' sign in failed', {
description: error?.message || 'Something went wrong, please try again.',
})
}
console.log('Supabase session error:', error)
}
})
const token = params.get('token') || ''
if (token) {
storeSessionToken(token)
postLogin()
}
}
} catch (error) {
toast.show(capitalizeWord(provider) + ' sign in failed', {
Expand All @@ -80,7 +65,7 @@ export const SignInScreen = (): React.ReactNode => {
}
}

const handleOAuthSignInWithPress = async (provider: Provider) => {
const handleOAuthSignInWithPress = async (provider: AuthProviderName) => {
if (provider === 'apple' && Platform.OS === 'ios') {
// use native sign in with apple in ios
await signInWithApple()
Expand Down
57 changes: 22 additions & 35 deletions packages/app/features/sign-up/screen.native.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import { AuthProviderName } from '@t4/api/src/auth/shared'
import { YStack, useToastController } from '@t4/ui'
import { SignUpSignInComponent } from 'app/features/sign-in/SignUpSignIn'
import { useRouter } from 'solito/router'
import type { Provider } from '@supabase/supabase-js'
import Constants from 'expo-constants'
import { capitalizeWord } from 'app/utils/string'
import { useSignIn, useSignUp } from 'app/utils/auth'
import { initiateAppleSignIn } from 'app/utils/auth/appleAuth'
import { storeSessionToken } from 'app/utils/auth/credentials'
import { isExpoGo } from 'app/utils/flags'
import { useSupabase } from 'app/utils/supabase/hooks/useSupabase'
import * as WebBrowser from 'expo-web-browser'
import { capitalizeWord } from 'app/utils/string'
import { trpc } from 'app/utils/trpc'
import { getInitialURL } from 'expo-linking'
import * as WebBrowser from 'expo-web-browser'
import { Platform } from 'react-native'
import { initiateAppleSignIn } from 'app/utils/auth/appleAuth'
import { useSignIn, useSignUp } from 'app/utils/auth'

export const SignInScreen = (): React.ReactNode => {
const { push } = useRouter()
const { signIn } = useSignIn()
const { signUp } = useSignUp()
const toast = useToastController()
const utils = trpc.useUtils()

const postLogin = () => {
utils.user.invalidate()
utils.auth.invalidate()
}

const signInWithApple = async () => {
try {
const { token, nonce } = await initiateAppleSignIn()
Expand All @@ -35,39 +40,21 @@ export const SignInScreen = (): React.ReactNode => {
}
}

const handleOAuthWithWeb = async (provider: Provider) => {
// FIXME hmm... probably could to host a screen with nextjs to handle this

const handleOAuthWithWeb = async (provider: AuthProviderName) => {
try {
const redirectUri = (await getInitialURL()) || 't4://'
const response = await WebBrowser.openAuthSessionAsync(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/auth/v1/authorize?provider=${provider}&redirect_to=${redirectUri}`,
process.env.NEXT_PUBLIC_APP_URL + '/oauth/' + provider,
redirectUri
)
if (response.type === 'success') {
const url = response.url
const params = new URLSearchParams(url.split('#')[1])
const accessToken = params.get('access_token') || ''
const refreshToken = params.get('refresh_token') || ''
await supabase.auth
.setSession({
access_token: accessToken,
refresh_token: refreshToken,
})
.then(({ data: { session }, error }) => {
if (session) {
// @ts-ignore set session does not call subscribers when session is updated
supabase.auth._notifyAllSubscribers('SIGNED_IN', session)
replace('/')
} else {
if (!isExpoGo) {
toast.show(capitalizeWord(provider) + ' sign in failed', {
description: error?.message || 'Something went wrong, please try again.',
})
}
console.log('Supabase session error:', error)
}
})
const token = params.get('token') || ''
if (token) {
storeSessionToken(token)
postLogin()
}
}
} catch (error) {
toast.show(capitalizeWord(provider) + ' sign in failed', {
Expand All @@ -78,7 +65,7 @@ export const SignInScreen = (): React.ReactNode => {
}
}

const handleOAuthSignInWithPress = async (provider: Provider) => {
const handleOAuthSignInWithPress = async (provider: AuthProviderName) => {
if (provider === 'apple' && Platform.OS === 'ios') {
// use native sign in with apple in ios
await signInWithApple()
Expand Down
2 changes: 2 additions & 0 deletions packages/app/utils/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ export type SignInWithAppleIdTokenAndNonce = {

export type SignInWithOAuth = {
provider: AuthProviderName
redirectTo?: string
}


Expand Down Expand Up @@ -171,6 +172,7 @@ export function useSignIn() {
utils.auth.invalidate()
}

// Might want to useMemo here...
const signIn = async (props: SignInProps) => {
if (isSignInWithEmailAndPassword(props)) {
const res = await mutation.mutateAsync(props)
Expand Down

0 comments on commit 837c9f1

Please sign in to comment.