The Cyberverse
The Cyberverse10mo ago

ctx.auth.getUserIdentity() returning undefined in webhook

hi, This doesn't work for me in the backend as I try to get the user identity in a webhook function, but it keeps returning null here is the code: export const webhookHandler = httpAction(async (ctx, request) => { const body = await request.json(); const { target, installation } = body; const userId = await getUserId(ctx); if (userId === undefined) { throw new ConvexError('User must be logged in.'); } switch (target) { case 'created': await handleInstallationCreated(ctx, installation); } return new Response(Webhook Recieved, { status: 200 }); });
No description
16 Replies
erquhart
erquhart10mo ago
Webhooks aren't authenticated, so they don't have access the to auth object. Services that send webhooks often provide some method of verification, you'll want to use that in place of auth for inbound webhooks.
The Cyberverse
The CyberverseOP10mo ago
Ok, thanks Lemme try it out But I have a question, I want to be able to update the details of the currently logged in user using the webhooks so I can store the installation id for my github app and use it to create an access token, since webhooks aren't authenticated
erquhart
erquhart10mo ago
The inbound webhooks won't have a connection to any ongoing user session, so you'll want to find a way to infer the related user from the info in the webhook.
The Cyberverse
The CyberverseOP10mo ago
Ok, that makes sense. I'll try that Also, when do I create a new user in the db based on the current details of the logged in user? Is it on start of the app, like should I use the isAuthenticated state from the useConvexAuth() hook to check if there's a current user and then create a new user based on those details? I don't know if I should use the email of the user to validate instead of the user id because it is not everybody that uses the same email for their github account and the user can either login with the github auth or google auth.
erquhart
erquhart10mo ago
Yep, you'll want to use their Clerk ID and store that in the users table, and the way you're describing works fine. Merging disparate accounts is functionality you'd have to wire up yourself, but it's definitely doable.
The Cyberverse
The CyberverseOP10mo ago
Can you give me like an Idea on how I would go about it, cos I don't have enough time, and I'm still working on auth for my app
erquhart
erquhart10mo ago
We're kind of getting into the basics of building an app, I can only help so much with your hackathon submission oh wait sorry I'm confusing you with another thread 🤦‍♂️ one sec For the merging disparate accounts part, you could honestly probably punt on that Whatever code you're using on the backend to handle sign in, you'll want to check for a user in the convex db in that function Are you using a template by chance?
The Cyberverse
The CyberverseOP10mo ago
Nope Maybe I should only allow github signins so I could just get it to work for now
erquhart
erquhart10mo ago
I would 100% recommend that So many startups take that approach for the same reason, you can get pretty far before you have to worry about improving it But you're saying when they sign in with github you don't have the user info in a convex table right? that may be a problem depending on what you're doing in your app and whether you require user info for queries and such I'm literally doing this in my frontend:
useEffect(() => {
if (isAuthenticated) {
updateUser()
}
}, [isAuthenticated])
useEffect(() => {
if (isAuthenticated) {
updateUser()
}
}, [isAuthenticated])
The Cyberverse
The CyberverseOP10mo ago
Hmmm, I'll try that
erquhart
erquhart10mo ago
Convex function body:
const identity = await ctx.auth.getUserIdentity()
if (!identity?.email) {
throw new Error('Not authenticated')
}
try {
const user = await getCurrent(ctx)
if (user.email !== identity?.email) {
await patchCurrent(ctx, {
email: identity.email,
})
}
} catch (e) {
if (e.cause?.type === 'NOT_FOUND') {
insert(ctx, {
clerkId: identity.subject,
email: identity.email,
})
return
}
throw e
}
const identity = await ctx.auth.getUserIdentity()
if (!identity?.email) {
throw new Error('Not authenticated')
}
try {
const user = await getCurrent(ctx)
if (user.email !== identity?.email) {
await patchCurrent(ctx, {
email: identity.email,
})
}
} catch (e) {
if (e.cause?.type === 'NOT_FOUND') {
insert(ctx, {
clerkId: identity.subject,
email: identity.email,
})
return
}
throw e
}
The Cyberverse
The CyberverseOP10mo ago
how does getCurrent() work? and is there a way to do something similar to upsert from mongodb in convex?
erquhart
erquhart10mo ago
Here's getCurrent():
export const getCurrent = async (ctx: QueryCtx) => {
const identity = await ctx.auth.getUserIdentity()
if (!identity) {
throw new Error('Not authenticated')
}
if (!identity.email) {
throw new Error(`signup has no email address: ${JSON.stringify(identity)}`)
}
const user = await ctx.db
.query('users')
.withIndex('by_clerkId', (q) => q.eq('clerkId', identity.subject))
.unique()
if (!user) {
throw new Error(`User not found for clerkId ${identity.subject}`, {
cause: { type: 'NOT_FOUND' },
})
}
return user
}
export const getCurrent = async (ctx: QueryCtx) => {
const identity = await ctx.auth.getUserIdentity()
if (!identity) {
throw new Error('Not authenticated')
}
if (!identity.email) {
throw new Error(`signup has no email address: ${JSON.stringify(identity)}`)
}
const user = await ctx.db
.query('users')
.withIndex('by_clerkId', (q) => q.eq('clerkId', identity.subject))
.unique()
if (!user) {
throw new Error(`User not found for clerkId ${identity.subject}`, {
cause: { type: 'NOT_FOUND' },
})
}
return user
}
This is just what I use, not a part of any helper lib or anything And yeah, that patchCurrent() is a function that's upserting - you just read the user, and if there's no user, your create one, otherwise patch
The Cyberverse
The CyberverseOP10mo ago
Thanks, I'll try that and get back to you It worked, I've found a way to replace the user id with the github username for the webhook validation But I'm having issues with my webhook, whenever I try to get an item in the headers, it doesn't work here is the headers: Headers { host: 'blissful-salamander-460.convex.site', 'user-agent': 'GitHub-Hookshot/b18fc72', 'content-length': '8803', accept: '/', 'content-type': 'application/json', 'x-forwarded-for': '140.82.115.80', 'x-forwarded-host': 'blissful-salamander-460.convex.site', 'x-forwarded-proto': 'http', 'x-github-delivery': '87872dc0-e30c-11ee-9e92-df8177a3b6a9', 'x-github-event': 'installation', 'x-github-hook-id': '465303810', 'x-github-hook-installation-target-id': '851970', 'x-github-hook-installation-target-type': 'integration', 'x-hub-signature': 'sha1=177f573870f64d63e599506461b957223c611584', 'x-hub-signature-256': 'sha256=d8f690ff24c9867e3032a54e7cfc36e8eb1128b8a2ebf693bdc2676e1152cee5', 'accept-encoding': 'gzip' } here is the way I access it: const receivedSignature = headers['x-hub-signature']; It's always saying undefined, I've tried every method to access it, but it doesn't work @erquhart Solved
erquhart
erquhart10mo ago
Sorry I missed this - for anyone else that sees this, how did you solve?
The Cyberverse
The CyberverseOP10mo ago
I created a new headers object from the request headers const header = new Headers(headers); then I used the get function to get the headers based on it's key: const receivedSignature = header.get('x-hub-signature');

Did you find this page helpful?