fireproof-storage / fireproof-storage/fireproof

ClerkClaimSchema rejects new signups missing optional Clerk profile fields (last_name etc.)

Open Beginner friendly
#1,814 0 comments 0 reactions 0 assignees View on GitHub
bug
Dominant language
TypeScript
Stars
973
Forks
58
PR merge metrics
No merged PRs in 30d

Description

## Problem

`ClerkClaimSchema` in [`@fireproof/core-types-base`](https://www.npmjs.com/package/@fireproof/core-types-base) declares `params.first`, `params.last`, `params.image_url`, and `params.name` as required strings:

```ts
// fp-clerk-claim.zod.ts
export const ClerkEmailTemplateClaimSchema = z.object({
// ...
first: z.string(),
image_url: z.string(),
last: z.string(),
name: z.string().nullable(),
// ...
});
```

But Clerk's JWT template variables for these attributes resolve to `undefined` when the user hasn't filled in the corresponding field. This happens routinely for:

- **Apple Sign In** — Apple hides `last_name` by default on subsequent sign-ins
- **Single-name users** — Google OAuth profiles with no last name
- **Email signups without a profile step** — `first` / `last` / `image_url` all absent

The schema then rejects the JWT with:

```
ZodError: [{
expected: "string",
code: "invalid_type",
path: ["params", "last"],
message: "Invalid input"
}]
```

Downstream effect: `ClerkApiToken.decode` and `ClerkApiToken.verify` both bail before returning the claim, so any product gating on `getTokenClaims()` (e.g. vibes.diy's first-prompt flow) silently fails for the affected user.

## Reproduction

```ts
import { ClerkClaimSchema } from "@fireproof/core-types-base";

// JWT-shaped claim from a brand-new Apple SSO signup
const claim = {
azp: "https://vibes.diy",
exp: 1900000000,
iat: 1899999400,
iss: "https://clerk.vibes.diy",
params: {
email: "newuser@example.com",
email_verified: true,
first: "Pat",
image_url: "https://img.clerk.com/...",
// last: undefined ← Clerk template var resolves to undefined
name: null,
public_meta: {},
},
role: "user",
sub: "user_2abc",
userId: "user_2abc",
};

const result = ClerkClaimSchema.safeParse(claim);
// result.success === false
// result.error.issues[0] === { code: "invalid_type", path: ["params","last"], ... }
```

## Suggested fix

Wrap the four cosmetic profile fields in `.catch(...)` so missing template variables fall through to safe defaults instead of erroring out, while keeping `email` / `email_verified` strict:

```ts
export const ClerkEmailTemplateClaimSchema = z.object({
// ...
first: z.string().catch(""),
image_url: z.string().catch(""),
last: z.string().catch(""),
name: z.string().nullable().catch(null),
// ...
});
```

`z.infer` is unchanged (`.catch` preserves the inner output type), so no downstream type drift.

## Workaround in vibes.diy

We're currently shipping a [pnpm patch](https://github.com/VibesDIY/vibes.diy/pull/1788) of `0.24.19` that applies exactly the diff above. Happy to drop it once an upstream release lands.

## Why this surfaced now

The schema-gated path has been live since Feb in vibes.diy and presumably others; what changed is the mix of inbound signups widened (Apple SSO + email-only paths grew), pushing the share of JWTs with undefined `params.last` above the visibility threshold.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start in fp-clerk-claim.zod.ts in @fireproof/core-types-base and reproduce the failure with ClerkClaimSchema.safeParse using the JWT-shaped claim shown in the issue. Update the four cosmetic profile fields so missing values use the stated safe defaults, while email and email_verified remain strict; done means the claim parses successfully without those optional fields.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
authentication
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.