-
Notifications
You must be signed in to change notification settings - Fork 0
support guest sign-in #44
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| "use client" | ||
|
|
||
| import { useAction } from "convex/react"; | ||
| import { useSignIn } from "@clerk/nextjs"; | ||
| import { useRouter } from "next/navigation"; | ||
| import { useState } from "react"; | ||
| import { api } from "@workspace/backend/_generated/api"; | ||
| import { Button } from "@workspace/ui/components/button"; | ||
| import { toast } from "sonner"; | ||
|
|
||
| const GuestSignInButton = () => { | ||
| const { signIn, setActive, isLoaded } = useSignIn(); | ||
| const createSignInTicket = useAction(api.public.guest.createSignInTicket); | ||
| const router = useRouter(); | ||
| const [isLoading, setIsLoading] = useState(false); | ||
|
|
||
| const handleGuestSignIn = async () => { | ||
| if (!isLoaded || isLoading) return; | ||
|
|
||
| setIsLoading(true); | ||
|
|
||
| try { | ||
| const { ticket } = await createSignInTicket({}); | ||
| const attempt = await signIn.create({ strategy: "ticket", ticket }); | ||
|
|
||
| if (attempt.status === "complete") { | ||
| await setActive({ session: attempt.createdSessionId }); | ||
| router.push("/"); | ||
| } else { | ||
| toast.error("Guest sign-in failed. Please try again."); | ||
| } | ||
| } catch (error) { | ||
| console.error(error); | ||
| toast.error("Guest sign-in is unavailable right now."); | ||
| } finally { | ||
| setIsLoading(false); | ||
| } | ||
| }; | ||
|
|
||
| return ( | ||
| <Button | ||
| className="w-full" | ||
| disabled={!isLoaded || isLoading} | ||
| onClick={handleGuestSignIn} | ||
| type="button" | ||
| variant="default" | ||
| > | ||
| {isLoading ? "Signing in..." : "Continue as Guest"} | ||
| </Button> | ||
| ); | ||
| }; | ||
|
|
||
| export default GuestSignInButton; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,13 @@ | ||
| import { SignIn } from '@clerk/nextjs' | ||
| import React from 'react' | ||
| import GuestSignInButton from '../components/GuestSignInButton' | ||
|
|
||
| export default function SignInView() { | ||
| return <SignIn routing="hash"/> | ||
| return ( | ||
| <div className="flex flex-col items-center gap-4"> | ||
| <SignIn routing="hash"/> | ||
| <GuestSignInButton/> | ||
| </div> | ||
| ) | ||
| } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import { ConvexError } from "convex/values"; | ||
| import { action } from "../_generated/server"; | ||
| import { createClerkClient } from "@clerk/backend"; | ||
|
|
||
| if (!process.env.CLERK_SECRET_KEY) { | ||
| throw new Error( | ||
| "CLERK_SECRET_KEY environment variable is required" | ||
| ) | ||
| } | ||
|
|
||
| const clerkClient = createClerkClient({ secretKey: process.env.CLERK_SECRET_KEY }); | ||
|
|
||
| /** | ||
| * Mints a single-use, short-lived Clerk sign-in ticket for the shared | ||
| * recruiter demo account (CLERK_GUEST_USER_ID). The client redeems it via | ||
| * `signIn.create({ strategy: "ticket", ticket })` to get a real, isolated | ||
| * Clerk session without ever handling a shared password. | ||
| * | ||
| * The guest account's org role (org:guest) is what actually protects the | ||
| * demo data — see checkUserIdentityAndGetOrgId's `requireWrite` guard. | ||
| */ | ||
| export const createSignInTicket = action({ | ||
| args: {}, | ||
|
|
||
| handler: async () => { | ||
| const guestUserId = process.env.CLERK_GUEST_USER_ID; | ||
|
|
||
| if (!guestUserId) { | ||
| throw new ConvexError({ | ||
| code: "NOT_CONFIGURED", | ||
| message: "Guest sign-in is not configured", | ||
| }); | ||
| } | ||
|
|
||
| const signInToken = await clerkClient.signInTokens.createSignInToken({ | ||
| userId: guestUserId, | ||
| expiresInSeconds: 60, | ||
| }); | ||
|
Comment on lines
+35
to
+38
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. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Verify the exact Clerk SDK declaration shipped for the locked version.
archive="$(mktemp)"
trap 'rm -f "$archive"' EXIT
curl -fsSL "$(npm view `@clerk/backend`@2.9.2 dist.tarball)" -o "$archive"
tar -tzf "$archive" | rg 'SignInTokenApi\.d\.ts$'
tar -xzf "$archive" -O "$(tar -tzf "$archive" | rg 'SignInTokenApi\.d\.ts$' | head -n1)"
# Trace the backend requirement for active organization claims.
ast-grep outline packages/backend/convex/private/checkUserIdentityAndGetOrgId.ts --items all
rg -n -C 4 'org(_id|Id)|org(_role|Role)|requireWrite' \
packages/backend/convex/private/checkUserIdentityAndGetOrgId.tsRepository: Rabinagurung/echo Length of output: 2288 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- guest action ---'
cat -n packages/backend/convex/public/guest.ts
printf '%s\n' '--- Clerk dependency declarations ---'
rg -n -C 3 '"`@clerk/backend`"|`@clerk/backend`' \
package.json packages package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null || true
printf '%s\n' '--- organization configuration and guest identity usage ---'
rg -n -C 4 'GUEST_ORG_ROLE|guestUserId|createSignInToken|signInToken|orgId|orgRole|CLERK' \
packages/backend README.md .env.example 2>/dev/null || trueRepository: Rabinagurung/echo Length of output: 50375 🏁 Script executed: #!/bin/bash
set -euo pipefail
archive="$(mktemp)"
trap 'rm -f "$archive"' EXIT
url="$(npm view `@clerk/backend`@2.29.3 dist.tarball)"
curl -fsSL "$url" -o "$archive"
entry="$(tar -tzf "$archive" | rg 'SignInTokenApi\.d\.ts$' | head -n1)"
printf '%s\n' "--- $entry ---"
tar -xzf "$archive" -O "$entry"
printf '%s\n' '--- all organization-related sign-in-token declarations ---'
tar -tzf "$archive" | rg -i 'sign.?in|organization' | rg '\.(d\.ts|js)$' | head -80Repository: Rabinagurung/echo Length of output: 1646 🌐 Web query:
💡 Result: To create a sign-in token that activates a specific organization upon redemption, use the Clerk Backend SDK or Backend API by providing the optional Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
archive="$(mktemp)"
trap 'rm -f "$archive"' EXIT
curl -fsSL "$(npm view `@clerk/backend`@2.29.3 dist.tarball)" -o "$archive"
printf '%s\n' '--- sign-in-token runtime files ---'
tar -tzf "$archive" | rg 'SignInTokenApi\.(js|mjs|cjs)$|SignInTokens\.(js|mjs|cjs)$'
printf '%s\n' '--- runtime references to orgId/org_id ---'
for entry in $(tar -tzf "$archive" | rg '\.(js|mjs|cjs)$'); do
body="$(tar -xzf "$archive" -O "$entry" 2>/dev/null || true)"
if printf '%s' "$body" | rg -q 'org_?id|orgId'; then
printf '%s\n' "--- $entry ---"
printf '%s' "$body" | rg -n -C 3 'org_?id|orgId'
fi
done
printf '%s\n' '--- package metadata ---'
tar -xzf "$archive" -O package/package.json | jq '{version,types,exports}'Repository: Rabinagurung/echo Length of output: 667 🌐 Web query:
💡 Result: The Citations: 🏁 Script executed: #!/bin/bash
set -euo pipefail
archive="$(mktemp)"
trap 'rm -f "$archive"' EXIT
curl -fsSL "$(npm view `@clerk/backend`@2.29.3 dist.tarball)" -o "$archive"
printf '%s\n' '--- archive entries containing sign-in ---'
tar -tzf "$archive" | rg -i 'sign.?in|token' | head -120
printf '%s\n' '--- archive entries containing organization ---'
tar -tzf "$archive" | rg -i 'organization' | head -80
printf '%s\n' '--- all textual orgId/org_id references ---'
while IFS= read -r entry; do
body="$(tar -xzf "$archive" -O "$entry" 2>/dev/null || true)"
if printf '%s' "$body" | rg -q 'org_?id|orgId'; then
printf '%s\n' "--- $entry ---"
printf '%s' "$body" | rg -n -C 2 'org_?id|orgId'
fi
done < <(tar -tzf "$archive" | rg '\.(js|mjs|cjs|d\.ts)$')Repository: Rabinagurung/echo Length of output: 4229 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- Clerk pull request metadata ---'
curl -fsSL https://api.github.com/repos/clerk/javascript/pulls/9192 |
jq '{number,title,state,merged_at,merge_commit_sha,base:.base.ref,head:.head.ref,body}'
printf '%s\n' '--- commits associated with the pull request ---'
curl -fsSL https://api.github.com/repos/clerk/javascript/pulls/9192/commits |
jq -r '.[] | [.sha,.commit.author.date,.commit.message] | `@tsv`'Repository: Rabinagurung/echo Length of output: 2221 🏁 Script executed: #!/bin/bash
set -euo pipefail
for version in 2.29.3 2.33.1 latest; do
archive="$(mktemp)"
url="$(npm view "`@clerk/backend`@$version" dist.tarball)"
curl -fsSL "$url" -o "$archive"
entry="$(tar -tzf "$archive" | rg 'SignInTokenApi\.d\.ts$' | head -n1)"
printf '%s\n' "--- `@clerk/backend`@$version ---"
tar -xzf "$archive" -O "$entry" | sed -n '1,30p'
rm -f "$archive"
doneRepository: Rabinagurung/echo Length of output: 4063 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- published version dates around the feature ---'
npm view `@clerk/backend` time --json |
jq -r 'to_entries[] | select(.key|test("^2\\.(2[89]|3[0-9])\\.")) | [.key,.value] | `@tsv`' |
tail -40Repository: Rabinagurung/echo Length of output: 1963 🏁 Script executed: #!/bin/bash
set -euo pipefail
versions="$(npm view `@clerk/backend` versions --json | jq -r '.[]' | rg '^2\.(33|34|35)\.[0-9]+$|^2\.[4-9][0-9]\.[0-9]+$' | sort -V)"
for version in $versions; do
archive="$(mktemp)"
if ! curl -fsSL "$(npm view "`@clerk/backend`@$version" dist.tarball)" -o "$archive"; then
rm -f "$archive"
continue
fi
entry="$(tar -tzf "$archive" | rg 'SignInTokenApi\.d\.ts$' | head -n1)"
declaration="$(tar -xzf "$archive" -O "$entry" 2>/dev/null || true)"
if printf '%s' "$declaration" | rg -q 'orgId'; then
printf '%s\n' "$version"
rm -f "$archive"
break
fi
rm -f "$archive"
done
printf '%s\n' '--- current published version ---'
npm view `@clerk/backend` versionRepository: Rabinagurung/echo Length of output: 4481 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- README guest sign-in section ---'
sed -n '170,210p' README.md
printf '%s\n' '--- guest action callers ---'
rg -n -C 5 'createSignInTicket|Continue as Guest|guest' \
apps packages --glob '!**/node_modules/**' --glob '!**/dist/**' | head -160Repository: Rabinagurung/echo Length of output: 11328 🏁 Script executed: #!/bin/bash
set -euo pipefail
npm view `@clerk/backend` versions --json |
jq -r '.[]' |
rg '^2\.(3[4-9]|[4-9][0-9])\.[0-9]+$|^3\.' |
sort -V |
tail -40Repository: Rabinagurung/echo Length of output: 1780 Activate the demo organization when creating the sign-in token.
🤖 Prompt for AI Agents |
||
|
|
||
| return { ticket: signInToken.token }; | ||
|
Comment on lines
+22
to
+40
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. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
# Locate existing request-abuse controls that could protect this public action.
rg -n -i -C 3 \
'rate.?limit|ratelimit|turnstile|captcha|bot.?protect|createSignInTicket' \
packages/backend/convex apps/webRepository: Rabinagurung/echo Length of output: 6808 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- guest action ---'
cat -n packages/backend/convex/public/guest.ts
printf '%s\n' '--- backend package versions ---'
rg -n -C 2 '"`@clerk/backend`"|"convex"' packages/backend/package.json package.json pnpm-lock.yaml yarn.lock package-lock.json 2>/dev/null || true
printf '%s\n' '--- Convex configuration and public action usage ---'
rg -n -i -C 3 'defineHTTP|httpRouter|public\.guest|createSignInTicket|useAction|auth\.getUserIdentity|CLERK_GUEST_USER_ID' packages/backend apps/web convex.json package.json 2>/dev/null || true
printf '%s\n' '--- relevant frontend flow ---'
cat -n apps/web/modules/auth/ui/components/GuestSignInButton.tsxRepository: Rabinagurung/echo Length of output: 15415 🌐 Web query:
💡 Result: Convex does not provide a built-in, platform-enforced public API rate limit that specifically targets anonymous requests [1]. Instead, Convex relies on application-layer rate limiting, which you implement within your own functions [2][3]. For protection against excessive anonymous traffic, you should use the official Convex Rate Limiter component [2][3]. This tool allows you to: 1. Define Granular Limits: You can create distinct rate limit configurations for anonymous vs. authenticated users, often applying stricter rules to anonymous requests [4][5]. 2. Identify Anonymous Users: Since anonymous users do not have a unique user ID, common strategies for applying these limits include using IP addresses (noting their limitations) or client-generated session IDs, ideally secured by a challenge like a CAPTCHA [6]. 3. Implement Programmatic Control: You call the rate limiter within your Convex mutations to check and consume tokens. If a request exceeds your defined limit, the component can throw an error or return a status indicating the request should be retried after a specified duration [2][7]. While Convex infrastructure itself imposes system-wide limits—such as concurrency constraints per deployment instance class—these are distinct from request-rate throttling [8][1]. Consequently, it is your responsibility to define and enforce rate limiting logic in your application code to protect your API endpoints from abuse [3][6]. Citations:
🌐 Web query:
💡 Result: Clerk Backend API rate limits are applied per application instance, identified by your Secret Key [1]. For the Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- all abuse-control and limiter references ---'
rg -n -i -C 2 \
'rate.?limit|ratelimit|RateLimiter|token bucket|turnstile|captcha|bot.?protect|client.?ip|remote.?addr|x-forwarded-for|ip address' \
. --glob '!**/node_modules/**' --glob '!**/dist/**' --glob '!**/build/**' || true
printf '%s\n' '--- action declarations and auth checks in public modules ---'
rg -n -C 4 \
'export const .* = action\(|ctx\.auth\.getUserIdentity|ctx\.runMutation|internal\.' \
packages/backend/convex/public packages/backend/convex/_generated 2>/dev/null || true
printf '%s\n' '--- Convex component/config files ---'
git ls-files | rg -i '(^|/)(convex\.json|components\.json|.*rate.*limit.*|.*convex.*config.*)$' || trueRepository: Rabinagurung/echo Length of output: 14253 🌐 Web query:
💡 Result: The provided documentation does not explicitly detail specific numerical rate limits for the Clerk Backend API or Secret Keys. While Clerk provides comprehensive documentation for their SDKs, CLI, and API references, the specific rate limit values for authentication requests are not publicly stated in the available resources [1][2][3][4]. It is recommended to contact Clerk support or check your Clerk Dashboard for information regarding your specific account's usage constraints and limits [3]. Citations:
🌐 Web query:
💡 Result: In the context of Convex actions, the ActionCtx provides an auth field that allows you to check user authentication status [1]. There is no direct access to the client's request IP address within the ActionCtx [1]. If you are performing a public (unauthenticated) action, you can still use the action context to run queries or mutations, but you should be aware that the context does not expose request-specific metadata like the IP address [1]. If your application requires handling IP-based restrictions or logging, you must manage that logic externally or through other mechanisms, as Convex actions do not provide the request IP in the function context [1]. If you need to allowlist Convex's own outbound IP addresses for external services, you can find information in the Convex networking documentation [1]. Citations: 🌐 Web query:
💡 Result: A rate limiter for mutations is a mechanism used to control the frequency of state-changing operations to prevent abuse and ensure system stability [1]. In contexts like Convex, rate limiters can be implemented as transactional components that integrate directly with the database system [1]. This ensures that checks and updates to rate limits occur atomically within the same transaction as the business logic, preventing race conditions and ensuring data consistency [1]. Key characteristics and implementation patterns for mutation rate limiters include: Transactional Integrity: By incorporating rate limit logic into the mutation transaction, the system can guarantee that the limit count is only incremented if the mutation succeeds; otherwise, the change rolls back [1]. Customizable Scoping: Effective rate limiters allow for granular control by using custom keys such as user IDs, IP addresses, or specific roles and subscription tiers [1]. Handling Violations: When a limit is reached, the system typically throws an error—such as a ConvexError—that the client can catch and handle [1]. Scalability: Advanced implementations use techniques like sharding across database partitions to handle high-traffic endpoints and reduce contention [1]. Fair Queuing: Some implementations employ fair queuing algorithms to maintain request ordering and prevent abusive traffic from completely blocking legitimate requests [1]. Citations: Add abuse protection before minting guest tickets.
🤖 Prompt for AI Agents |
||
| }, | ||
| }); | ||
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.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: Rabinagurung/echo
Length of output: 4102
🏁 Script executed:
Repository: Rabinagurung/echo
Length of output: 50373
🏁 Script executed:
Repository: Rabinagurung/echo
Length of output: 50374
🌐 Web query:
Clerk Convex getUserIdentity orgRole org_role organization role JWT claim💡 Result:
To access Clerk organization roles in Convex, you must include the relevant organization claims (such as
org_role) in your Clerk JWT template [1][2]. By default, thectx.auth.getUserIdentity()method in Convex only provides standard OIDC claims and the information explicitly included in the JWT token sent to the Convex backend [3][4]. If you do not explicitly addorg_role(or other organization-related fields likeorg_id) to the JWT template used by the Convex integration, those fields will not be available in the identity object returned by Convex [1][4]. Follow these steps to ensure organization roles are accessible: 1. Configure the JWT Template: In the Clerk Dashboard, navigate to the JWT Templates section and ensure you have a template namedconvex(or that your current integration is correctly configured) [5][6]. 2. Map Custom Claims: In the Sessions/JWT Template configuration, add the required organization claims [2][6]. You can use Clerk's available shortcodes (e.g.,{{user.organization.role}}or similar depending on your specific requirements) to inject dynamic values into the JWT claims [2][5]. 3. Access in Convex: Once the claims are part of the JWT, you can access them within your Convex functions after callingctx.auth.getUserIdentity()[3][7]: const identity = await ctx.auth.getUserIdentity; if (identity!== null) { const orgRole = identity.org_role as string | undefined; // Use the role for authorization logic } Note that recent versions of the Convex React integration may default to sending raw session tokens rather than template tokens if the audience matches [1][8]. If you find your custom claims are missing, verify that your client-side implementation is correctly requesting the JWT token using the configured template [1][8]. Accessing roles directly via the identity object is the recommended approach for server-side authorization [4][2].Citations:
🏁 Script executed:
Repository: Rabinagurung/echo
Length of output: 50374
🌐 Web query:
site:docs.convex.dev Clerk Convex identity.org_id identity.org_role💡 Result:
When integrating Clerk with Convex, there are no specific, built-in fields named
identity.org_idoridentity.org_roleautomatically exposed on theUserIdentityobject returned byctx.auth.getUserIdentity()[1][2][3]. TheUserIdentityobject is derived from the JWT provided by Clerk [3]. While it contains standard OIDC claims (such assubject,issuer,email, etc.) and any custom claims configured in your Clerk application, organizational data likeorg_idandorg_roleare not standard defaults [2][3]. To access organization information within your Convex functions, you must configure Clerk to include these as custom claims in the JWT [2]. 1. Configure Clerk Claims: Use Clerk's dashboard or API to add organization IDs and roles to the JWT template that Convex uses [2][3]. 2. Accessing in Convex: Once configured, these custom claims will be available within your Convex functions through theUserIdentityobject [2][3]. Because these are custom claims, you should assert their types in TypeScript [3]:typescript // Example: Accessing custom claims in a Convex function export const myQuery = query({ handler: async (ctx) => { const identity = await ctx.auth.getUserIdentity(); if (!identity) throw new Error("Unauthenticated"); // Access custom claims (ensure these match your Clerk JWT template) const orgId = identity["org_id"] as string | undefined; const orgRole = identity["org_role"] as string | undefined; return { orgId, orgRole }; }, });Note: Distinguish between Convex platform management (teams/roles for your developers managing your Convex projects, accessed via the Convex dashboard) and your application's user authentication (Clerk integration) [4][5]. The fields you are asking about typically relate to your application's user organization membership, not your team membership on the Convex platform itself [4][5].Citations:
🌐 Web query:
Clerk JWT template org_role org_id claim exact names organization role session token💡 Result:
To include an organization's ID and role in a Clerk JWT template, you should use the shortcode variables
{{org.id}}and{{org.role}}within the Claims JSON editor in the Clerk Dashboard [1]. While older documentation sometimes referred to these as{{organization.id}}and{{organization.role}}, the current recommended practice is to use the{{org.*}}shortcode format [1]. Both forms function, but{{org.*}}is the standard for new templates [1]. When configuring the Claims object in your JWT template, you can map these to any key name you prefer (e.g.,org_id,org_role) [1][2]. A typical configuration in the JWT template JSON editor would look like this: { "org_id": "{{org.id}}", "org_role": "{{org.role}}", "org_slug": "{{org.slug}}", "org_permissions": "{{org.permissions}}" } Key considerations for these claims: - Organization context: These values are only included in the session token if the user has an active organization session [3][4]. If no organization is active, these fields will be null [1]. - Session Activation: If you are not seeing these claims in your token, ensure that the user has an active organization session by callingsetActive({ organization: orgId })on the client side [4]. - TypeScript Support: To maintain type safety when accessing these custom claims in your application, you can define a global interface in aglobals.d.tsfile [5][6]: declare global { interface CustomJwtSessionClaims { org_id?: string org_role?: string // Add other custom claims here } } These claims are distinct from Clerk's default session token claims, which are provided automatically when an organization is active, but are not added by default to custom JWT templates unless you explicitly define them using the template editor [3][4].Citations:
Use the configured JWT claim name for the guest role. Clerk organization claims use configured keys, commonly
org_role, while this code readsidentity.orgRole. Unless theconvexJWT template explicitly emitsorgRolewith value"org:guest",requireWrite: truewill not block guest writes.🤖 Prompt for AI Agents