Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion apps/dashboard/app/(app)/api/auth/refresh/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ export async function POST(request: Request) {
// Get the current token from the request
const currentToken = request.headers.get("x-current-token");
if (!currentToken) {
console.error("Session refresh failed: no current token");
return Response.json({ success: false, error: "Failed to refresh session" }, { status: 401 });
}
// Call refreshSession logic here and get new token
Expand Down
11 changes: 2 additions & 9 deletions apps/dashboard/app/api/auth/accept-invitation/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,7 @@ export async function POST(request: NextRequest) {
let invitation: Invitation | null;
try {
invitation = await auth.getInvitation(invitationToken);
} catch (error) {
console.error("Failed to retrieve invitation:", {
error: error instanceof Error ? error.message : "Unknown error",
});
} catch (_error) {
return NextResponse.json(
{ success: false, error: "Invalid or expired invitation token" },
{ status: 400 },
Expand Down Expand Up @@ -96,9 +93,6 @@ export async function POST(request: NextRequest) {

return response;
} catch (error) {
console.error("Failed to accept invitation:", {
error: error instanceof Error ? error.message : "Unknown error",
});
return NextResponse.json(
{
success: false,
Expand All @@ -107,8 +101,7 @@ export async function POST(request: NextRequest) {
{ status: 500 },
);
}
} catch (error) {
console.error("Error in accept invitation API:", error);
} catch (_error) {
return NextResponse.json({ success: false, error: "Internal server error" }, { status: 500 });
}
}
5 changes: 1 addition & 4 deletions apps/dashboard/app/api/auth/invitation/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,7 @@ export async function POST(request: NextRequest) {
success: true,
organizationId: result.organizationId,
});
} catch (error) {
console.error("Error processing invitation:", {
error: error instanceof Error ? error.message : "Unknown error",
});
} catch (_error) {
return NextResponse.json(
{ success: false, error: "Internal server error" },
{ status: 500, headers: { "Cache-Control": "no-store" } },
Expand Down
155 changes: 99 additions & 56 deletions apps/dashboard/app/auth/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
type NavigationResponse,
type OAuthResult,
PENDING_SESSION_COOKIE,
type PendingTurnstileResponse,
type SignInViaOAuthOptions,
type UserData,
type VerificationResult,
Expand All @@ -38,6 +39,39 @@ function getRequestMetadata() {
return { ipAddress, userAgent };
}

// Turnstile verification helper
async function verifyTurnstileToken(token: string): Promise<boolean> {
const environment = env();
const secretKey = environment.CLOUDFLARE_TURNSTILE_SECRET_KEY;

if (!secretKey) {
return false;
}

try {
const response = await fetch("https://challenges.cloudflare.com/turnstile/v0/siteverify", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
secret: secretKey,
response: token,
}),
});

if (!response.ok) {
return false;
}

const data = await response.json();

return data.success === true;
} catch (_error) {
return false;
}
}

// Authentication Actions
export async function signUpViaEmail(params: UserData): Promise<EmailAuthResult> {
const metadata = getRequestMetadata();
Expand Down Expand Up @@ -99,9 +133,8 @@ export async function verifyAuthCode(params: {
});
redirectUrl = `/join/success?${params.toString()}`;
}
} catch (error) {
} catch (_error) {
// Don't fail the redirect if we can't get org name
console.warn("Could not fetch organization name for success page:", error);
}

return {
Expand All @@ -112,10 +145,7 @@ export async function verifyAuthCode(params: {
}
}
}
} catch (error) {
console.error("Failed to auto-select invited organization:", {
error: error instanceof Error ? error.message : "Unknown error",
});
} catch (_error) {
// Fall through to return the original result if auto-selection fails
}
}
Expand All @@ -137,12 +167,8 @@ export async function verifyAuthCode(params: {
if (invitation.state === "pending") {
try {
await auth.acceptInvitation(invitation.id);
} catch (acceptError) {
// Log but don't fail - invitation might already be accepted
console.warn("Could not accept invitation (might already be accepted):", {
invitationId: invitation.id,
error: acceptError instanceof Error ? acceptError.message : "Unknown error",
});
} catch (_acceptError) {
// Don't fail - invitation might already be accepted
}
}

Expand All @@ -160,9 +186,8 @@ export async function verifyAuthCode(params: {
const params = new URLSearchParams({ from_invite: "true" });
redirectUrl = `/join/success?${params.toString()}`;
}
} catch (error) {
} catch (_error) {
// Don't fail if we can't get org name, just use join success without org name
console.warn("Could not fetch organization name for new user success page:", error);
const params = new URLSearchParams({ from_invite: "true" });
redirectUrl = `/join/success?${params.toString()}`;
}
Expand All @@ -173,24 +198,13 @@ export async function verifyAuthCode(params: {
cookies: [],
};
}
console.warn("Invalid invitation or missing organization ID:", {
hasInvitation: !!invitation,
organizationId: invitation?.organizationId,
});
} catch (error) {
console.error("Failed to process invitation for new user:", {
error: error instanceof Error ? error.message : "Unknown error",
invitationToken: `${invitationToken.substring(0, 10)}...`,
});
} catch (_error) {
// Fall through to return original result
}
}

return result;
} catch (error) {
console.error("Failed to verify auth code:", {
error: error instanceof Error ? error.message : "Unknown error",
});
} catch (_error) {
return {
success: false,
code: AuthErrorCode.UNKNOWN_ERROR,
Expand All @@ -206,7 +220,6 @@ export async function verifyEmail(code: string): Promise<VerificationResult> {
const token = await getCookie(PENDING_SESSION_COOKIE);

if (!token) {
console.error("Pending auth token missing or expired");
return {
success: false,
code: AuthErrorCode.UNKNOWN_ERROR,
Expand All @@ -221,10 +234,7 @@ export async function verifyEmail(code: string): Promise<VerificationResult> {
}

return result;
} catch (error) {
console.error("Failed to verify email:", {
error: error instanceof Error ? error.message : "Unknown error",
});
} catch (_error) {
return {
success: false,
code: AuthErrorCode.UNKNOWN_ERROR,
Expand All @@ -237,7 +247,6 @@ export async function resendAuthCode(email: string): Promise<EmailAuthResult> {
const envVars = env();
const unkeyRootKey = envVars.UNKEY_ROOT_KEY;
if (!unkeyRootKey) {
console.error("UNKEY_ROOT_KEY environment variable is not set");
return {
success: false,
code: AuthErrorCode.UNKNOWN_ERROR,
Expand All @@ -250,8 +259,7 @@ export async function resendAuthCode(email: string): Promise<EmailAuthResult> {
duration: "5m",
limit: 5,
rootKey: unkeyRootKey,
onError: (err: Error) => {
console.error("Rate limiting error:", err.message);
onError: (_err: Error) => {
return { success: true, limit: 0, remaining: 1, reset: 1 };
},
});
Expand Down Expand Up @@ -360,11 +368,8 @@ export async function completeOrgSelection(
// Store the last used organization ID in a cookie for auto-selection on next login
try {
await setLastUsedOrgCookie({ orgId });
} catch (error) {
console.error("Failed to set last used org cookie in completeOrgSelection:", {
orgId,
error: error instanceof Error ? error.message : "Unknown error",
});
} catch (_error) {
// Ignore cookie setting errors
}
}

Expand All @@ -384,18 +389,12 @@ export async function switchOrg(orgId: string): Promise<{ success: boolean; erro
// Store the last used organization ID in a cookie for auto-selection on next login
try {
await setLastUsedOrgCookie({ orgId });
} catch (error) {
console.error("Failed to set last used org cookie in switchOrg:", {
orgId,
error: error instanceof Error ? error.message : "Unknown error",
});
} catch (_error) {
// Ignore cookie setting errors
}

return { success: true };
} catch (error) {
console.error("Organization switch failed:", {
error: error instanceof Error ? error.message : "Unknown error",
});
return {
success: false,
error: error instanceof Error ? error.message : "Failed to switch organization",
Expand Down Expand Up @@ -426,25 +425,69 @@ export async function acceptInvitationAndJoin(
await setSessionCookie({ token: newToken, expiresAt });
try {
await setLastUsedOrgCookie({ orgId: organizationId });
} catch (error) {
console.error("Failed to set last used org cookie in acceptInvitationAndJoin:", {
orgId: organizationId,
error: error instanceof Error ? error.message : "Unknown error",
});
} catch (_error) {
// Ignore cookie setting errors
}

return { success: true };
} catch (error) {
console.error("Failed to accept invitation and join organization:", {
error: error instanceof Error ? error.message : "Unknown error",
});
return {
success: false,
error: error instanceof Error ? error.message : "Failed to join organization",
};
}
}

/**
* Verify Turnstile token and retry original auth operation
*/
export async function verifyTurnstileAndRetry(params: {
turnstileToken: string;
email: string;
challengeParams: PendingTurnstileResponse["challengeParams"];
userData?: { firstName: string; lastName: string }; // Only for sign-up
}): Promise<EmailAuthResult> {
const { turnstileToken, email, challengeParams, userData } = params;

// Verify Turnstile token
const isValidToken = await verifyTurnstileToken(turnstileToken);

if (!isValidToken) {
return {
success: false,
code: AuthErrorCode.UNKNOWN_ERROR,
message: "Verification failed. Please try again.",
};
}

// Retry original auth operation based on action
const metadata = getRequestMetadata();

if (challengeParams.action === "sign-up" && userData) {
// For sign-up, we need the user data
return await auth.signUpViaEmail({
...userData,
email,
...metadata,
bypassRadar: true,
});
}
if (challengeParams.action === "sign-in") {
// For sign-in, we just need the email
return await auth.signInViaEmail({
email,
...metadata,
bypassRadar: true,
});
}

return {
success: false,
code: AuthErrorCode.UNKNOWN_ERROR,
message: "Invalid challenge parameters.",
};
}

/**
* Check if a pending session exists (for workspace selection flow)
* This is needed because PENDING_SESSION_COOKIE is HttpOnly and not accessible from client
Expand Down
Loading