Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .changeset/twelve-shoes-behave.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@flags-sdk/vercel": minor
"@vercel/prepare-flags-definitions": minor
"@vercel/flags-core": minor
---

Add OIDC authentication support for Vercel Flags clients and generated flag definitions.

`@vercel/flags-core` can now create clients without an SDK key and authenticate with a Vercel OIDC token, while still supporting SDK keys and connection strings. Bundled definitions can be looked up by SDK key hash or OIDC project ID.

`@vercel/prepare-flags-definitions` now collects both SDK keys and `VERCEL_OIDC_TOKEN`, fetches definitions for each auth entry, deduplicates identical definitions across SDK keys and OIDC project IDs, and writes generated maps keyed by SDK key hash or project ID.

`@flags-sdk/vercel` now supports provider data lookup for Vercel flag origins that do not include an SDK key, allowing OIDC-backed clients to resolve project metadata.
18 changes: 8 additions & 10 deletions packages/adapter-vercel/src/index.ts
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

createVercelAdapter arg needs to be optional, such that it can default to the oidc one

Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ export type VercelAdapterDeclaration<ValueType, EntitiesType> = Omit<
*/
export function createVercelAdapter(
// usually a connection string, but can also be a pre-configured FlagsClient
sdkKeyOrFlagsClient: string | FlagsClient,
sdkKeyOrFlagsClient?: string | FlagsClient,
) {
const flagsClient =
typeof sdkKeyOrFlagsClient === 'string'
typeof sdkKeyOrFlagsClient === 'string' || sdkKeyOrFlagsClient === undefined
? createClient(sdkKeyOrFlagsClient)
: sdkKeyOrFlagsClient;

Expand Down Expand Up @@ -86,9 +86,9 @@ export function vercelAdapter<ValueType, EntitiesType>(): Adapter<
return defaultVercelAdapter<ValueType, EntitiesType>();
}

const flagsClients = new Map<string, FlagsClient>();
const flagsClients = new Map<string | undefined, FlagsClient>();

function getOrCreateClient(sdkKey: string): FlagsClient {
function getOrCreateClient(sdkKey?: string): FlagsClient {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's non-obvious that this will now also work for undefined (the OIDC use case).

Suggested change
function getOrCreateClient(sdkKey?: string): FlagsClient {
/**
* Ensures we only ever create a single client per SDK Key
*
* When undefined is passed, due to OIDC being used, then we return a single client too.
**/
function getOrCreateClient(sdkKey?: string): FlagsClient {

let client = flagsClients.get(sdkKey);
if (!client) {
client = createClient(sdkKey);
Expand All @@ -99,14 +99,12 @@ function getOrCreateClient(sdkKey: string): FlagsClient {

function isVercelOrigin(
origin: unknown,
): origin is { provider: 'vercel'; sdkKey: string } {
): origin is { provider: 'vercel'; sdkKey?: string } {
return (
typeof origin === 'object' &&
origin !== null &&
'provider' in origin &&
(origin as Record<string, unknown>).provider === 'vercel' &&
'sdkKey' in origin &&
typeof (origin as Record<string, unknown>).sdkKey === 'string'
(origin as Record<string, unknown>).provider === 'vercel'
);
}

Expand All @@ -122,14 +120,14 @@ export async function getProviderData(
.filter((i): i is KeyedFlagDefinitionType => !Array.isArray(i));

// Collect unique sdkKeys and resolve their projectIds
const sdkKeys = new Set<string>();
const sdkKeys = new Set<string | undefined>();
for (const d of flagDefs) {
if (isVercelOrigin(d.origin)) {
sdkKeys.add(d.origin.sdkKey);
}
}

const projectIdBySdkKey = new Map<string, string>();
const projectIdBySdkKey = new Map<string | undefined, string>();
await Promise.all(
Array.from(sdkKeys).map(async (sdkKey) => {
const client = getOrCreateClient(sdkKey);
Expand Down
2 changes: 1 addition & 1 deletion packages/prepare-flags-definitions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const result = await prepareFlagsDefinitions({
});

if (result.created) {
console.log(`Bundled definitions for ${result.sdkKeysCount} SDK keys`);
console.log(`Bundled definitions for ${result.entryCount} SDK keys`);
} else {
console.log(`No definitions created: ${result.reason}`);
}
Expand Down
Loading