All files / src/api/drive callback.ts

100% Statements 23/23
100% Branches 16/16
100% Functions 1/1
100% Lines 23/23

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105          56x                                                                             56x 51x 51x   51x 21x     30x 24x 24x   24x 3x     21x 3x         18x 18x             9x 9x           9x   9x 6x             3x             6x 6x          
import { createRoute, RouteHandler, z } from '@hono/zod-openapi'
import { ErrorSchema, AuthCallbackResponseSchema } from '../../schema/drive'
import { EnvHono } from '../..'
import { exchangeCodeForTokens } from '../../utils/oauth'
 
export const callbackRoute = createRoute({
  method: 'get',
  path: '/callback',
  request: {
    query: z.object({
      code: z.string().describe('Authorization code from Google OAuth'),
      scope: z.string().optional().describe('OAuth scope'),
      error: z.string().optional().describe('OAuth error if any')
    })
  },
  responses: {
    200: {
      content: {
        'application/json': {
          schema: AuthCallbackResponseSchema
        }
      },
      description: 'OAuth callback processed successfully'
    },
    400: {
      content: {
        'application/json': {
          schema: ErrorSchema
        }
      },
      description: 'Bad request or OAuth error'
    },
    500: {
      content: {
        'application/json': {
          schema: ErrorSchema
        }
      },
      description: 'Internal server error'
    }
  },
  tags: ['Authentication']
})
 
export const callbackHandler: RouteHandler<typeof callbackRoute, EnvHono> = async (c) => {
  try {
    const { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET } = c.env
 
    if (!GOOGLE_CLIENT_ID || !GOOGLE_CLIENT_SECRET) {
      return c.json({ error: 'Missing required environment variables' }, 400)
    }
 
    const url = new URL(c.req.url)
    const code = url.searchParams.get('code')
    const error = url.searchParams.get('error')
 
    if (error) {
      return c.json({ error: 'OAuth error', details: error }, 400)
    }
 
    if (!code) {
      return c.json({ error: 'Missing authorization code' }, 400)
    }
 
    // Exchange authorization code for tokens using type-safe function
    let tokens
    try {
      tokens = await exchangeCodeForTokens({
        clientId: GOOGLE_CLIENT_ID,
        clientSecret: GOOGLE_CLIENT_SECRET,
        code: code,
        redirectUri: 'http://localhost:3000/api/drive/callback'
      })
    } catch (error) {
      console.error('Token exchange failed:', error)
      return c.json({
        error: 'Failed to exchange authorization code for tokens',
        details: error instanceof Error ? error.message : 'Unknown error'
      }, 500)
    }
 
    console.log('Received tokens:', tokens)
 
    if (!tokens.refresh_token) {
      return c.json({
        success: false,
        message: 'No refresh token received. This may happen if the app was already authorized. Try revoking access at https://myaccount.google.com/permissions and re-authorize.',
        accessToken: tokens.access_token ? 'Access token received' : 'No access token'
      }, 200)
    }
 
    return c.json({
      success: true,
      refreshToken: tokens.refresh_token,
      message: `Refresh Token obtained! Add this to your .dev.vars file: GOOGLE_REFRESH_TOKEN=${tokens.refresh_token}`
    }, 200)
 
  } catch (error) {
    console.error('OAuth callback error:', error)
    return c.json({
      error: 'Failed to exchange code for tokens',
      details: error instanceof Error ? error.message : 'Unknown error'
    }, 500)
  }
}