diff --git a/.changeset/fast-frogs-impress.md b/.changeset/fast-frogs-impress.md new file mode 100644 index 00000000..5f39c90f --- /dev/null +++ b/.changeset/fast-frogs-impress.md @@ -0,0 +1,5 @@ +--- +'@supabase/auth-helpers-shared': patch +--- + +Add new to string method to the base error class diff --git a/.changeset/tough-apples-trade.md b/.changeset/tough-apples-trade.md new file mode 100644 index 00000000..c848481f --- /dev/null +++ b/.changeset/tough-apples-trade.md @@ -0,0 +1,6 @@ +--- +'@supabase/auth-helpers-nextjs': patch +'@supabase/auth-helpers-sveltekit': patch +--- + +Logger can be imported in your own project for setting log levels diff --git a/packages/nextjs/src/handlers/user.ts b/packages/nextjs/src/handlers/user.ts index 49be45b9..3c82dcd1 100644 --- a/packages/nextjs/src/handlers/user.ts +++ b/packages/nextjs/src/handlers/user.ts @@ -4,15 +4,15 @@ import { COOKIE_OPTIONS, jwtDecoder, TOKEN_REFRESH_MARGIN, - CookieNotParsed, JWTPayloadFailed, AuthHelperError, ErrorPayload, - AccessTokenNotFound + AccessTokenNotFound, + CookieNotFound } from '@supabase/auth-helpers-shared'; import type { NextApiRequest, NextApiResponse } from 'next'; import getUser from '../utils/getUser'; -import log from 'loglevel'; +import logger from '../utils/log'; interface ResponsePayload { user: null; @@ -32,7 +32,7 @@ export default async function handleUser( ) { try { if (!req.cookies) { - throw new CookieNotParsed(); + throw new CookieNotFound(); } const cookieOptions = { ...COOKIE_OPTIONS, ...options.cookieOptions }; const tokenRefreshMargin = @@ -81,12 +81,16 @@ export default async function handleUser( } catch (e) { let response: ResponsePayload = { user: null, accessToken: null }; if (e instanceof JWTPayloadFailed) { + logger.info('JWTPayloadFailed error has happened!'); response.error = e.toObj(); + } else if (e instanceof CookieNotFound) { + logger.warn(e.toString()); } else if (e instanceof AuthHelperError) { - log.debug(e.toObj()); + logger.info('AuthHelperError error has happened!'); + logger.error(e.toString()); } else { const error = e as ApiError; - log.debug(error.message); + logger.error(error.message); } res.status(200).json(response); } diff --git a/packages/nextjs/src/index.ts b/packages/nextjs/src/index.ts index 102d3bb9..37e95abd 100644 --- a/packages/nextjs/src/index.ts +++ b/packages/nextjs/src/index.ts @@ -8,3 +8,4 @@ export { default as withPageAuth } from './utils/withPageAuth'; export { default as withApiAuth } from './utils/withApiAuth'; export { default as supabaseServerClient } from './utils/supabaseServerClient'; export { supabaseClient, SupabaseClient } from './utils/initSupabase'; +export { default as logger } from './utils/log'; diff --git a/packages/nextjs/src/utils/getUser.ts b/packages/nextjs/src/utils/getUser.ts index d7ac8d08..41e3db87 100644 --- a/packages/nextjs/src/utils/getUser.ts +++ b/packages/nextjs/src/utils/getUser.ts @@ -13,13 +13,20 @@ import { NextRequestAdapter, NextResponseAdapter, jwtDecoder, - CookieNotParsed, JWTPayloadFailed, AccessTokenNotFound, RefreshTokenNotFound, - AuthHelperError + AuthHelperError, + CookieNotFound, + ErrorPayload } from '@supabase/auth-helpers-shared'; -import log from 'loglevel'; +import logger from '../utils/log'; + +interface ResponsePayload { + user: User | null; + accessToken: string | null; + error?: ErrorPayload; +} export interface GetUserOptions { cookieOptions?: CookieOptions; @@ -32,7 +39,7 @@ export default async function getUser( | GetServerSidePropsContext | { req: NextApiRequest; res: NextApiResponse }, options: GetUserOptions = { forceRefresh: false } -): Promise<{ user: User | null; accessToken: string | null; error?: string }> { +): Promise { try { if ( !process.env.NEXT_PUBLIC_SUPABASE_URL || @@ -43,7 +50,7 @@ export default async function getUser( ); } if (!context.req.cookies) { - throw new CookieNotParsed(); + throw new CookieNotFound(); } const supabase = createClient( process.env.NEXT_PUBLIC_SUPABASE_URL, @@ -69,12 +76,14 @@ export default async function getUser( if (options.forceRefresh || jwtUser.exp < timeNow + tokenRefreshMargin) { // JWT is expired, let's refresh from Gotrue if (!refresh_token) throw new RefreshTokenNotFound(); + logger.info('Refreshing access token...'); const { data, error } = await supabase.auth.api.refreshAccessToken( refresh_token ); if (error) { throw error; } else { + logger.info('Saving tokens to cookies...'); setCookies( new NextRequestAdapter(context.req), new NextResponseAdapter(context.res), @@ -93,6 +102,7 @@ export default async function getUser( return { user: data!.user!, accessToken: data!.access_token }; } } else { + logger.info('Getting the user object from the database...'); const { user, error: getUserError } = await supabase.auth.api.getUser( access_token ); @@ -102,10 +112,19 @@ export default async function getUser( return { user: user!, accessToken: access_token }; } } catch (e) { - const error = e as ApiError; - if (e instanceof AuthHelperError) { - log.debug(e.toObj()); + let response: ResponsePayload = { user: null, accessToken: null }; + if (e instanceof JWTPayloadFailed) { + logger.info('JWTPayloadFailed error has happened!'); + response.error = e.toObj(); + } else if (e instanceof CookieNotFound) { + logger.warn(e.toString()); + } else if (e instanceof AuthHelperError) { + logger.info('AuthHelperError error has happened!'); + logger.error(e.toString()); + } else { + const error = e as ApiError; + logger.error(error.message); } - return { user: null, accessToken: null, error: error.message }; + return response; } } diff --git a/packages/nextjs/src/utils/log.ts b/packages/nextjs/src/utils/log.ts new file mode 100644 index 00000000..37192bd4 --- /dev/null +++ b/packages/nextjs/src/utils/log.ts @@ -0,0 +1,6 @@ +import * as loglevel from 'loglevel'; + +const logger = loglevel.getLogger('@supabase/auth-helpers-nextjs'); +logger.info('Starting @supabase/auth-helpers-nextjs logging...'); + +export default logger; diff --git a/packages/nextjs/tsup.config.ts b/packages/nextjs/tsup.config.ts index aa9549ed..4d407335 100644 --- a/packages/nextjs/tsup.config.ts +++ b/packages/nextjs/tsup.config.ts @@ -3,7 +3,6 @@ import type { Options } from 'tsup'; export const tsup: Options = { dts: true, entryPoints: ['src/index.ts', 'src/middleware/index.ts'], - // `aws-amplify` is external, but sub-dependencies weren't automatically externalized ("require" statements were included) external: ['next', 'react', /^@supabase\//], format: ['cjs'], // inject: ['src/react-shim.js'], diff --git a/packages/react/src/components/UserProvider.tsx b/packages/react/src/components/UserProvider.tsx index 8e9ad6e5..b15e93b7 100644 --- a/packages/react/src/components/UserProvider.tsx +++ b/packages/react/src/components/UserProvider.tsx @@ -95,16 +95,18 @@ export const UserProvider = (props: Props) => { setUser(user); // Set up auto token refresh if (autoRefreshToken) { - let timeout = 20 * 1000; - const expiresAt = (user as any)?.exp; - if (expiresAt) { - const timeNow = Math.round(Date.now() / 1000); - const expiresIn = expiresAt - timeNow; - const refreshDurationBeforeExpires = - expiresIn > TOKEN_REFRESH_MARGIN ? TOKEN_REFRESH_MARGIN : 0.5; - timeout = (expiresIn - refreshDurationBeforeExpires) * 1000; + if (user) { + let timeout = 20 * 1000; + const expiresAt = (user as any).exp; + if (expiresAt) { + const timeNow = Math.round(Date.now() / 1000); + const expiresIn = expiresAt - timeNow; + const refreshDurationBeforeExpires = + expiresIn > TOKEN_REFRESH_MARGIN ? TOKEN_REFRESH_MARGIN : 0.5; + timeout = (expiresIn - refreshDurationBeforeExpires) * 1000; + } + setTimeout(checkSession, timeout); } - setTimeout(checkSession, timeout); } if (!user) setIsLoading(false); } catch (_e) { diff --git a/packages/react/tsup.config.ts b/packages/react/tsup.config.ts index ce8dfce6..87ff1291 100644 --- a/packages/react/tsup.config.ts +++ b/packages/react/tsup.config.ts @@ -3,7 +3,6 @@ import type { Options } from 'tsup'; export const tsup: Options = { dts: true, entryPoints: ['src/index.tsx'], - // `aws-amplify` is external, but sub-dependencies weren't automatically externalized ("require" statements were included) external: ['react', /^@supabase\//], format: ['cjs'], // inject: ['src/react-shim.js'], diff --git a/packages/shared/src/utils/errors.ts b/packages/shared/src/utils/errors.ts index ec1c79a0..6306d3a2 100644 --- a/packages/shared/src/utils/errors.ts +++ b/packages/shared/src/utils/errors.ts @@ -21,6 +21,10 @@ export class AuthHelperError extends Error { source: this.source }; } + + toString() { + return JSON.stringify(this.toObj()); + } } export class CookieNotFound extends AuthHelperError { diff --git a/packages/shared/tsup.config.ts b/packages/shared/tsup.config.ts index 60efeab3..6ad29e22 100644 --- a/packages/shared/tsup.config.ts +++ b/packages/shared/tsup.config.ts @@ -3,7 +3,6 @@ import type { Options } from 'tsup'; export const tsup: Options = { dts: true, entryPoints: ['src/index.ts'], - // `aws-amplify` is external, but sub-dependencies weren't automatically externalized ("require" statements were included) external: ['react', 'next', /^@supabase\//], format: ['cjs', 'esm'], // inject: ['src/react-shim.js'], diff --git a/packages/svelte/src/SupaAuthHelper.svelte b/packages/svelte/src/SupaAuthHelper.svelte index 2c916b96..8f84f011 100644 --- a/packages/svelte/src/SupaAuthHelper.svelte +++ b/packages/svelte/src/SupaAuthHelper.svelte @@ -5,6 +5,7 @@ import { checkSession, type Session } from './helpers'; import { setIsLoading, setError, user, accessToken } from './store'; import { dequal } from 'dequal'; + import { CallbackUrlFailed } from '@supabase/auth-helpers-shared'; // Props export let supabaseClient: SupabaseClient; @@ -63,8 +64,8 @@ body: JSON.stringify({ event, session }) }).then((res) => { if (!res.ok) { - const err = new Error(`The request to ${callbackUrl} failed`); - setError(err); + const err = new CallbackUrlFailed(callbackUrl); + setError(err.message); } }); // Fetch the user from the API route diff --git a/packages/svelte/src/helpers.ts b/packages/svelte/src/helpers.ts index aff679f3..dcf5ecb1 100644 --- a/packages/svelte/src/helpers.ts +++ b/packages/svelte/src/helpers.ts @@ -74,7 +74,7 @@ export const checkSession = async (props: CheckSessionArgs): Promise => { ); return; } - setError(new Error(error)); + setError(new Error(error).message); } networkRetries = 0; if (accessToken) { @@ -85,16 +85,18 @@ export const checkSession = async (props: CheckSessionArgs): Promise => { // Set up auto token refresh if (autoRefreshToken) { - let timeout = 20 * 1000; - const expiresAt = (user as UserExtra).exp; - if (expiresAt) { - const timeNow = Math.round(Date.now() / 1000); - const expiresIn = expiresAt - timeNow; - const refreshDurationBeforeExpires = - expiresIn > TOKEN_REFRESH_MARGIN ? TOKEN_REFRESH_MARGIN : 0.5; - timeout = (expiresIn - refreshDurationBeforeExpires) * 1000; + if (user) { + let timeout = 20 * 1000; + const expiresAt = (user as UserExtra).exp; + if (expiresAt) { + const timeNow = Math.round(Date.now() / 1000); + const expiresIn = expiresAt - timeNow; + const refreshDurationBeforeExpires = + expiresIn > TOKEN_REFRESH_MARGIN ? TOKEN_REFRESH_MARGIN : 0.5; + timeout = (expiresIn - refreshDurationBeforeExpires) * 1000; + } + setTimeout(checkSession, timeout); } - setTimeout(checkSession, timeout); } } catch (_e) { const err = new CallbackUrlFailed(profileUrl); diff --git a/packages/svelte/src/store.ts b/packages/svelte/src/store.ts index 019454c7..41f0c82f 100644 --- a/packages/svelte/src/store.ts +++ b/packages/svelte/src/store.ts @@ -27,8 +27,8 @@ const setAccessToken = (token: string) => accessToken.set(token); const isLoading = writable(initialValues.isLoading); const setIsLoading = (loading: boolean) => isLoading.set(loading); -const error = writable(initialValues.error); -const setError = (err: ErrorPayload) => error.set(err); +const error = writable(initialValues.error); +const setError = (err: ErrorPayload | string) => error.set(err); const resetAll = () => { setUser(initialValues.user); diff --git a/packages/sveltekit/src/handlers/user.ts b/packages/sveltekit/src/handlers/user.ts index ad06b6c3..6f8da703 100644 --- a/packages/sveltekit/src/handlers/user.ts +++ b/packages/sveltekit/src/handlers/user.ts @@ -9,11 +9,12 @@ import { CookieNotParsed, JWTPayloadFailed, AuthHelperError, - AccessTokenNotFound + AccessTokenNotFound, + CookieNotFound } from '@supabase/auth-helpers-shared'; import { skHelper } from '../instance'; import { getUser, saveTokens } from '../utils/getUser'; -import log from 'loglevel'; +import logger from '../utils/log'; export interface HandleUserOptions { cookieOptions?: CookieOptions; @@ -27,7 +28,7 @@ export const handleUser = (options: HandleUserOptions = {}) => { try { if (!req.headers.has('cookie')) { - throw new CookieNotParsed(); + throw new CookieNotFound(); } const cookieOptions = { ...COOKIE_OPTIONS, ...options.cookieOptions }; @@ -35,6 +36,9 @@ export const handleUser = (options: HandleUserOptions = {}) => { options.tokenRefreshMargin ?? TOKEN_REFRESH_MARGIN; const cookies = parseCookie(req.headers.get('cookie')); + if (!cookies) { + throw new CookieNotParsed(); + } const access_token = cookies[`${cookieOptions.name}-access-token`]; if (!access_token) { @@ -83,13 +87,18 @@ export const handleUser = (options: HandleUserOptions = {}) => { } } catch (e) { if (e instanceof JWTPayloadFailed) { + logger.info('JWTPayloadFailed error has happened!'); event.locals.error = e.toObj(); + } else if (e instanceof CookieNotFound) { + logger.warn(e.toString()); } else if (e instanceof AuthHelperError) { - log.debug(e.toObj()); + logger.info('AuthHelperError error has happened!'); + logger.error(e.toString()); } else { const error = e as ApiError; - log.debug(error.message); + logger.error(error.message); } + event.locals.user = null; event.locals.accessToken = null; return await resolve(event); diff --git a/packages/sveltekit/src/index.ts b/packages/sveltekit/src/index.ts index f27711ba..f6a9b5dc 100644 --- a/packages/sveltekit/src/index.ts +++ b/packages/sveltekit/src/index.ts @@ -2,9 +2,15 @@ export type { User, SupabaseClient } from '@supabase/supabase-js'; // Methods -export { handleCallback, handleUser, handleLogout, handleAuth } from './handlers/index'; +export { + handleCallback, + handleUser, + handleLogout, + handleAuth +} from './handlers/index'; export { default as getUserAndSaveTokens, getUser } from './utils/getUser'; export { default as withApiAuth } from './utils/withApiAuth'; export { default as withPageAuth } from './utils/withPageAuth'; export { default as supabaseServerClient } from './utils/supabaseServerClient'; export { skHelper } from './instance'; +export { default as logger } from './utils/log'; diff --git a/packages/sveltekit/src/utils/getUser.ts b/packages/sveltekit/src/utils/getUser.ts index a860f37a..f8a2da14 100644 --- a/packages/sveltekit/src/utils/getUser.ts +++ b/packages/sveltekit/src/utils/getUser.ts @@ -15,10 +15,12 @@ import { JWTPayloadFailed, RefreshTokenNotFound, AuthHelperError, - CookieNotSaved + CookieNotSaved, + CookieNotFound, + type ErrorPayload } from '@supabase/auth-helpers-shared'; import type { RequestResponse } from '../types'; -import log from 'loglevel'; +import logger from './log'; export interface GetUserOptions { cookieOptions?: CookieOptions; @@ -30,7 +32,7 @@ interface UserResponse { user: User | null; accessToken: string | null; refreshToken?: string; - error?: string; + error?: ErrorPayload | string; } /** @@ -56,7 +58,7 @@ export async function getUser( } if (!req.headers.has('cookie')) { - throw new CookieNotParsed(); + throw new CookieNotFound(); } const cookieOptions = { ...COOKIE_OPTIONS, ...options.cookieOptions }; @@ -64,6 +66,9 @@ export async function getUser( options.tokenRefreshMargin ?? TOKEN_REFRESH_MARGIN; const cookies = parseCookie(req.headers.get('cookie')); + if (!cookies) { + throw new CookieNotParsed(); + } const supabase = createClient(supabaseUrl, supabaseAnonKey); const access_token = cookies[`${cookieOptions.name}-access-token`]; @@ -88,6 +93,7 @@ export async function getUser( throw new RefreshTokenNotFound(); } + logger.info('Refreshing access token...'); const { data, error } = await supabase.auth.api.refreshAccessToken( refresh_token ); @@ -101,6 +107,7 @@ export async function getUser( refreshToken: data?.refresh_token }; } else { + logger.info('Getting the user object from the database...'); const { user, error: getUserError } = await supabase.auth.api.getUser( access_token ); @@ -110,11 +117,21 @@ export async function getUser( return { user, accessToken: access_token }; } } catch (e) { - const error = e as ApiError; - if (e instanceof AuthHelperError) { - log.debug(e.toObj()); + let response: UserResponse = { user: null, accessToken: null }; + if (e instanceof JWTPayloadFailed) { + logger.info('JWTPayloadFailed error has happened!'); + response.error = e.toObj(); + } else if (e instanceof CookieNotFound) { + logger.warn(e.toString()); + } else if (e instanceof AuthHelperError) { + logger.info('AuthHelperError error has happened!'); + logger.error(e.toString()); + } else { + const error = e as ApiError; + logger.error(error.message); } - return { user: null, accessToken: null, error: error.message }; + + return response; } } @@ -151,6 +168,7 @@ export function saveTokens( throw new RefreshTokenNotFound(); } + logger.info('Saving tokens to cookies...'); setCookies( new SvelteKitRequestAdapter(req), new SvelteKitResponseAdapter(res), @@ -169,11 +187,18 @@ export function saveTokens( return { user: session.user, accessToken: session.accessToken }; } } catch (e) { - const error = e as ApiError; - if (e instanceof AuthHelperError) { - log.debug(e.toObj()); + let response: UserResponse = { user: null, accessToken: null }; + if (e instanceof JWTPayloadFailed) { + logger.info('JWTPayloadFailed error has happened!'); + response.error = e.toObj(); + } else if (e instanceof AuthHelperError) { + logger.info('AuthHelperError error has happened!'); + logger.error(e.toString()); + } else { + const error = e as ApiError; + logger.error(error.message); } - return { user: null, accessToken: null, error: error.message }; + return response; } } diff --git a/packages/sveltekit/src/utils/log.ts b/packages/sveltekit/src/utils/log.ts new file mode 100644 index 00000000..897ca8a5 --- /dev/null +++ b/packages/sveltekit/src/utils/log.ts @@ -0,0 +1,6 @@ +import * as loglevel from 'loglevel'; + +const logger = loglevel.getLogger('@supabase/auth-helpers-sveltekit'); +logger.info('Starting @supabase/auth-helpers-sveltekit logging...'); + +export default logger;