Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 469
feat(electron): Introduce Electron SDK#8786
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Uh oh!
There was an error while loading. Please reload this page.
Changes from all commits
90ee35fcf4646d9a066646a80b9d5fc39285118a933edba01d8942d37bf45d1575f57e71f06ee3f8e6d8fb840d8757a100d690bc5e1e879fc64f20911afaaf15b3d43d9e80d1c0ebc7849cba8d2c884e66e260a97060388531d8d2File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading. Please reload this page.
Jump to
Uh oh!
There was an error while loading. Please reload this page.
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@clerk/electron': patch | ||
| --- | ||
| Introduce `@clerk/electron` package. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| --- | ||
| '@clerk/clerk-js': patch | ||
| --- | ||
| Display native OAuth callback errors in the UI. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| --- | ||
| '@clerk/localizations': patch | ||
| '@clerk/shared': patch | ||
| '@clerk/ui': patch | ||
| --- | ||
| Add localization support for OAuth access denied errors. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -19,6 +19,38 @@ type ClerkWithResourceCallback = { | ||||||||||||||||||||||||||||||
| ) => Promise<unknown>; | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| const NATIVE_OAUTH_FAILED_STATUS = 'failed'; | ||||||||||||||||||||||||||||||
| const NATIVE_OAUTH_ERROR_FALLBACK_CODE = 'oauth_callback_failed'; | ||||||||||||||||||||||||||||||
| const NATIVE_OAUTH_ERROR_MESSAGES: Record<string, string> = { | ||||||||||||||||||||||||||||||
| oauth_access_denied: 'You did not grant access to your account.', | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| function getNativeOAuthCallbackFailure(callbackUrl: string): { code: string; message: string } | null { | ||||||||||||||||||||||||||||||
| const searchParams = new URL(callbackUrl).searchParams; | ||||||||||||||||||||||||||||||
| const status = searchParams.get('__clerk_status'); | ||||||||||||||||||||||||||||||
| if (status !== NATIVE_OAUTH_FAILED_STATUS) { | ||||||||||||||||||||||||||||||
| return null; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| const unsafeCode = searchParams.get('__clerk_error_code') || NATIVE_OAUTH_ERROR_FALLBACK_CODE; | ||||||||||||||||||||||||||||||
| const code = NATIVE_OAUTH_ERROR_MESSAGES[unsafeCode] ? unsafeCode : NATIVE_OAUTH_ERROR_FALLBACK_CODE; | ||||||||||||||||||||||||||||||
| return { | ||||||||||||||||||||||||||||||
| code, | ||||||||||||||||||||||||||||||
| message: NATIVE_OAUTH_ERROR_MESSAGES[code] || 'OAuth callback failed.', | ||||||||||||||||||||||||||||||
Comment on lines
+36
to
+41
Contributor There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Harden error-code allowlisting against inherited object keys.
Suggested fix- const unsafeCode = searchParams.get('__clerk_error_code') || NATIVE_OAUTH_ERROR_FALLBACK_CODE;- const code = NATIVE_OAUTH_ERROR_MESSAGES[unsafeCode] ? unsafeCode : NATIVE_OAUTH_ERROR_FALLBACK_CODE;+ const unsafeCode = searchParams.get('__clerk_error_code') || NATIVE_OAUTH_ERROR_FALLBACK_CODE;+ const code = Object.prototype.hasOwnProperty.call(NATIVE_OAUTH_ERROR_MESSAGES, unsafeCode)+ ? unsafeCode+ : NATIVE_OAUTH_ERROR_FALLBACK_CODE;
@@
- message: NATIVE_OAUTH_ERROR_MESSAGES[code] || 'OAuth callback failed.',+ message: NATIVE_OAUTH_ERROR_MESSAGES[code] ?? 'OAuth callback failed.',As per coding guidelines, “Validate all inputs and sanitize outputs” applies because 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSource: Coding guidelines | ||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| async function resetFailedAttempt(resource: SignInResource | SignUpResource): Promise<void> { | ||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||
| // Both resources accept `{}` to reset the attempt, but their `create` param types differ. | ||||||||||||||||||||||||||||||
| await (resource.create as (params: Record<string, never>) => Promise<unknown>)({}); | ||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||
| // Best-effort: the OAuth failure is still thrown, so a failed reset just keeps the prior behavior. | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| export async function _authenticateWithTransport(opts: { | ||||||||||||||||||||||||||||||
| clerk: ClerkWithResourceCallback; | ||||||||||||||||||||||||||||||
| transport: OAuthTransport; | ||||||||||||||||||||||||||||||
| @@ -41,6 +73,15 @@ export async function _authenticateWithTransport(opts: { | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| const { callbackUrl } = await opts.transport.open(new URL(verificationUrl.toString())); | ||||||||||||||||||||||||||||||
| const failure = getNativeOAuthCallbackFailure(callbackUrl); | ||||||||||||||||||||||||||||||
| if (failure) { | ||||||||||||||||||||||||||||||
| // The failed verification persists on the client and would resurface on the next reload (the native | ||||||||||||||||||||||||||||||
| // flow never navigates away from the card), so reset the attempt before surfacing the error. | ||||||||||||||||||||||||||||||
| await resetFailedAttempt(opts.resource); | ||||||||||||||||||||||||||||||
| throw new ClerkRuntimeError(failure.message, { code: failure.code }); | ||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||
| const nonce = new URL(callbackUrl).searchParams.get('rotating_token_nonce'); | ||||||||||||||||||||||||||||||
| if (nonce) { | ||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| target/ | ||
| *.node | ||
| artifacts/ | ||
| # napi-generated type defs for the raw native binding (the public surface is | ||
| # the hand-written index.js/index.d.ts loader) | ||
| native.d.ts |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a
resource.reloadnegative assertion in the reset-failure test.This branch should also explicitly verify no reload occurs after
createrejects, matching the other failure-path tests.Suggested fix
expect(resource.create).toHaveBeenCalledWith({}); + expect(resource.reload).not.toHaveBeenCalled(); expect(clerk.__internal_handleResourceCallback).not.toHaveBeenCalled();As per coding guidelines, “Verify proper error handling and edge cases” applies for this failure path.
📝 Committable suggestion
🤖 Prompt for AI Agents
Source: Coding guidelines