From 6193a967a8d07605889446e9eabab149a275ee54 Mon Sep 17 00:00:00 2001 From: Stojan Dimitrovski Date: Sun, 27 Oct 2024 13:32:38 +0100 Subject: [PATCH] feat: do not send non-JWTs in `Authorization` header --- src/SupabaseClient.ts | 17 ++++++++++--- src/lib/fetch.ts | 7 ++++-- src/lib/helpers.ts | 57 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 76 insertions(+), 5 deletions(-) diff --git a/src/SupabaseClient.ts b/src/SupabaseClient.ts index 9f0a32c7..124a3353 100644 --- a/src/SupabaseClient.ts +++ b/src/SupabaseClient.ts @@ -19,7 +19,12 @@ import { DEFAULT_REALTIME_OPTIONS, } from './lib/constants' import { fetchWithAuth } from './lib/fetch' -import { stripTrailingSlash, applySettingDefaults } from './lib/helpers' +import { + stripTrailingSlash, + applySettingDefaults, + isJWT, + checkAuthorizationHeader, +} from './lib/helpers' import { SupabaseAuthClient } from './lib/SupabaseAuthClient' import { Fetch, GenericSchema, SupabaseClientOptions, SupabaseAuthClientOptions } from './lib/types' @@ -96,6 +101,8 @@ export default class SupabaseClient< this.storageKey = settings.auth.storageKey ?? '' this.headers = settings.global.headers ?? {} + checkAuthorizationHeader(this.headers) + if (!settings.accessToken) { this.auth = this._initSupabaseAuthClient( settings.auth ?? {}, @@ -285,10 +292,14 @@ export default class SupabaseClient< headers?: Record, fetch?: Fetch ) { - const authHeaders = { - Authorization: `Bearer ${this.supabaseKey}`, + const authHeaders: { [header: string]: string } = { apikey: `${this.supabaseKey}`, } + + if (isJWT(this.supabaseKey)) { + authHeaders.Authorization = `Bearer ${this.supabaseKey}` + } + return new SupabaseAuthClient({ url: this.authUrl, headers: { ...authHeaders, ...headers }, diff --git a/src/lib/fetch.ts b/src/lib/fetch.ts index c3ec5f42..e81432d1 100644 --- a/src/lib/fetch.ts +++ b/src/lib/fetch.ts @@ -1,5 +1,6 @@ // @ts-ignore import nodeFetch, { Headers as NodeFetchHeaders } from '@supabase/node-fetch' +import { isJWT } from './helpers' type Fetch = typeof fetch @@ -31,15 +32,17 @@ export const fetchWithAuth = ( const fetch = resolveFetch(customFetch) const HeadersConstructor = resolveHeadersConstructor() + const defaultAccessToken = isJWT(supabaseKey) ? supabaseKey : null + return async (input, init) => { - const accessToken = (await getAccessToken()) ?? supabaseKey + const accessToken = (await getAccessToken()) ?? defaultAccessToken let headers = new HeadersConstructor(init?.headers) if (!headers.has('apikey')) { headers.set('apikey', supabaseKey) } - if (!headers.has('Authorization')) { + if (!headers.has('Authorization') && accessToken) { headers.set('Authorization', `Bearer ${accessToken}`) } diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index d1161d0c..f001f0ba 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -66,3 +66,60 @@ export function applySettingDefaults< return result } + +export const BASE64URL_REGEX = /^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$/i + +/** + * Checks that the value somewhat looks like a JWT, does not do any additional parsing or verification. + */ +export function isJWT(value: string): boolean { + if (value.startsWith('Bearer ')) { + value = value.substring('Bearer '.length) + } + + value = value.trim() + + if (!value) { + return false + } + + const parts = value.split('.') + + if (parts.length !== 3) { + return false + } + + for (let i = 0; i < parts.length; i += 1) { + const part = parts[i] + + if (part.length < 4 || !BASE64URL_REGEX.test(part)) { + return false + } + } + + return true +} + +export function checkAuthorizationHeader(headers: { [header: string]: string }) { + if (headers.authorization && headers.Authorization) { + console.warn( + '@supabase-js: Both `authorization` and `Authorization` headers specified in createClient options. `Authorization` will be used.' + ) + + delete headers.authorization + } + + const authorization = headers.Authorization ?? headers.authorization ?? null + + if (!authorization) { + return + } + + if (authorization.startsWith('Bearer ') && authorization.length > 'Bearer '.length) { + if (!isJWT(authorization)) { + throw new Error( + '@supabase-js: createClient called with global Authorization header that does not contain a JWT' + ) + } + } +}