From 971da61e7b6b4b3fa67e521edf347264207842ae Mon Sep 17 00:00:00 2001 From: Lars Hampe Date: Mon, 30 Sep 2024 20:57:20 +0200 Subject: [PATCH] feat(api): add clerk webhook security check --- apps/api/package.json | 3 ++- apps/api/src/user/index.ts | 9 +++++++-- apps/api/tsconfig.json | 24 ++++++++++++++++++++++-- bun.lockb | Bin 394152 -> 398856 bytes 4 files changed, 31 insertions(+), 5 deletions(-) diff --git a/apps/api/package.json b/apps/api/package.json index dc8c48e..1323cba 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -11,7 +11,8 @@ "@hono/clerk-auth": "^2.0.0", "@hono/zod-openapi": "^0.16.2", "@scalar/hono-api-reference": "^0.5.149", - "hono": "^4.6.3" + "hono": "^4.6.3", + "svix": "^1.36.0" }, "devDependencies": { "@types/bun": "latest" diff --git a/apps/api/src/user/index.ts b/apps/api/src/user/index.ts index c3ba76c..26e3cf6 100644 --- a/apps/api/src/user/index.ts +++ b/apps/api/src/user/index.ts @@ -1,5 +1,6 @@ -import { OpenAPIHono, type z } from '@hono/zod-openapi' +import { OpenAPIHono } from '@hono/zod-openapi' import { HTTPException } from 'hono/http-exception' +import { Webhook } from 'svix' import type { Variables } from '..' import get from './get' import webhook from './webhook' @@ -21,7 +22,11 @@ app.openapi(get.route, async (c) => { app.openapi(webhook.route, async (c) => { try { - const result = await webhook.func({ payload: await c.req.json() }) + const wh = new Webhook(import.meta.env.CLERK_WEBHOOK_SECRET as string) + const payload = await c.req.json() + const headers = c.req.header() + const verifiedPayload = wh.verify(JSON.stringify(payload), headers) + const result = await webhook.func({ payload: verifiedPayload }) return c.json(result, 200) } catch (error) { if (error instanceof HTTPException) { diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json index c442b33..59a229d 100644 --- a/apps/api/tsconfig.json +++ b/apps/api/tsconfig.json @@ -1,7 +1,27 @@ { "compilerOptions": { - "strict": true, + // Enable latest features + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", "jsx": "react-jsx", - "jsxImportSource": "hono/jsx" + "allowJs": true, + + // Bundler mode + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + // Best practices + "strict": true, + "skipLibCheck": true, + "noFallthroughCasesInSwitch": true, + + // Some stricter flags + "noUnusedLocals": true, + "noUnusedParameters": true, + "noPropertyAccessFromIndexSignature": true } } \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 5084c700680c535e2a7b4f709e7d8bfb1aa1d2bf..78b76b2e754bb16c1cd4daa1b2af59e14d06ca90 100755 GIT binary patch delta 6148 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
FS{TeD2mrv(M73NyvdZ8q+a4hee5MdujzMQ zmi89K8?2%h`YrZ*t<IHr84GpTI!&WxFS7apLPx?{Wi)$ zWuQV(DTwxOCqeWw(1YkjaSU_O+OZket^SHcEFM zZYGAisXSG#`bk-`0^7g3W2OdCcU;yuOnog=Yeyo6JF5n$&>AsyzD&&@SX@;uD%bbD zF;jcUFlQjmF?HEYjV4%ttI}XHHRepcDZ?aJ72e9G7M-a}1&fQyS%vqysflOmV;SZc zk~6jMOdTx49Cw;yYVetQT(AsGQ@N7K)ax^~yI^uN-fL9CG|lwbgy^MfHjTZ#Y(r67 zF?;}0K-#8$ps6J$5f>r>O54;iG&RlWLdIykC%9tmXGz6ur9^29-U2{!Q$wv z+pCK*`eTAKYJ`tZur~`YmW4>Z@?kl}vJR3wA9ku(wn!4`%YseQP%?s;yCky>ZgL0vzsG>j-vB1M%6qBbr)Ii4 zL!L@Qap)EkG}EJ$1}r5eGC3vMD6^8YvQAyh5llB%5x||5l*+q1z7GJG_lnX*r8X`i zpGHeVQC08t_c3XS3{M}WloB;Hc6yw0nkHdxR1(vq$-DOc8>EfZBxq@_xt{@K=RcP_ zw4kRHW@5?#to22C4_mWh!D6PdwG-tIc4lRT*-TU7N6yk0%DZ*^k4FKH#%F@1-;_J( z4>rl?%B{Y$G46UQZJ48I;lM_S7M-lD+Zw^$ZCSR&q7z#qTI5^Ra8PJx^wdZ!oUMX* LFF&;%dA|KOz4$yN delta 3652 zcmeH~c~n$&6vuyW83Ys+(ZmH%b3Gu7S~CbJxPS^GWbSE_rDQ4YnblGBB$~&gH0e(> z&&jRQ(3CMA%fPURf*~-tW?Ak_rljSPCH1|`^_=>@)9H_XXYSm4Kli@-e(!hZz4x0{ zS-ziT_^uD`6*VRQk>|Jn*e1HEDUi}r}1Ghe05&qD2_)VeL z;;@#`aS@7W4sB(91=T z<&se3bi(w8xsDua_DF^IJiI2>n7bBtI0NA5){&- z659|Nf(Q!7nmy{_MZ(jp3cSEb*GXO8GZO~)OF zrg7Xzc}}tYx2BZQZmwdqo2zEU$|$=UuNZQ*T^%P=b5$>Si$Ii{T`zY;h@U~(})ncQ6ru*weLRRT~hCzJqK z3IPfU9FZQS0M`l3Dg~%C@=DdX1S4>Z)?G1@8q`{RSE?YzJHh^|MZsY)B@FB<-E4T3R<(GAtd^;{J(GNnrMi5kS1j_g1;CdOc9 zI_!>(M2I0^bemHO-ODs$( zk-)4On3>r~W_aq03NDc-lc~fggxUhPfLIWs$xa8Oi@;MK4^ez!%;XZ@uwQQ^LcAuo zQHfay(P4Obh?N{l*FmVt9Ajbuw+1t!I zgRS88c}HHL5=%MsG(tyU(^$r=E9{lDP#EdVy1}LkrLkOw)u6-*xRlic5&@f5{9R7( z30vj#mC$7Nf@sWE%XBL79$fm-20~1*TS2dZrm{hh-8_r6%zA@4z^F6pq?1al=Mo`6 zBs4Oa;iWFZAk>-nnUQ~#m)I&h*P_G+9O{eEa)hw=5F5a#$^9S!oSwyb&w!1`5>p5F z%DGfxABW69@kpmee!&b|0x=y8#9l@02YU)S95REOdRUfF3F-o^1@;_b4>z!a8D94y zi&-VJD6qZEj!JJQN_@qkfe7VrzpKC~D;lx}jD8DF&WnM46E=-HnL;Iwb7(M7OW4%N z6U>Id_GWgH*-$V~*woxp@&J`M4VU^B2XTW<&Hb9wu@MrVBS_6X%WOF8Y&rnr8>!Tx z#J3!Z2cjJWjdRQrVBdvc-zB~S!$*vSbV43=>w-+C5*NX#=qN}ouAA26GM7k#UBK&e zg;_F~o%62BZB*hKhejhr7ZMA7of-CU!iM@Z`5Vl}z@~i;jUVJ4DnYADO&te$0HMkM z$ZS09SJ9e8=v&Mtz@EuXy)9#oYd%ZsIW!5O*&O1LZ*`EWObH7P55}`;r$x4^))UnFa5=SF z_f@_6%O%zNGS%K+{!TnDLXN1>KUA#|@@kFVN4*ju+tn8X6xcN5S&oEkb klU`1}y}ae)X7ik*x>7$@DRpeq>+Iv-pew6e8@Ee;0{s*Ug8%>k