From a1bf5ec1c3680679704267febabe1642391e1e57 Mon Sep 17 00:00:00 2001 From: Meg Stepp Date: Mon, 31 Mar 2025 13:04:48 -0400 Subject: [PATCH 1/6] create a global baseUrl function and update the sso-callback and trpc --- .../sso-callback/[[...sso-callback]]/route.ts | 3 ++- apps/dashboard/lib/trpc/server.ts | 16 +--------------- apps/dashboard/lib/utils.ts | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/apps/dashboard/app/auth/sso-callback/[[...sso-callback]]/route.ts b/apps/dashboard/app/auth/sso-callback/[[...sso-callback]]/route.ts index 4f510e1339..757bfd80db 100644 --- a/apps/dashboard/app/auth/sso-callback/[[...sso-callback]]/route.ts +++ b/apps/dashboard/app/auth/sso-callback/[[...sso-callback]]/route.ts @@ -1,6 +1,7 @@ import { setCookiesOnResponse } from "@/lib/auth/cookies"; import { auth } from "@/lib/auth/server"; import { AuthErrorCode, SIGN_IN_URL } from "@/lib/auth/types"; +import { getBaseUrl } from "@/lib/utils"; import { type NextRequest, NextResponse } from "next/server"; export async function GET(request: NextRequest) { const authResult = await auth.completeOAuthSignIn(request); @@ -34,7 +35,7 @@ export async function GET(request: NextRequest) { } // Get base URL from request because Next.js wants it - const baseUrl = new URL(request.url).origin; + const baseUrl = getBaseUrl(); const response = NextResponse.redirect(new URL(authResult.redirectTo, baseUrl)); return await setCookiesOnResponse(response, authResult.cookies); diff --git a/apps/dashboard/lib/trpc/server.ts b/apps/dashboard/lib/trpc/server.ts index dbfc998fdc..d8c9c7204b 100644 --- a/apps/dashboard/lib/trpc/server.ts +++ b/apps/dashboard/lib/trpc/server.ts @@ -2,21 +2,7 @@ import { createTRPCProxyClient, httpLink } from "@trpc/client"; import superjson from "superjson"; import type { Router } from "./routers"; - -function getBaseUrl() { - if (typeof window !== "undefined") { - // browser should use relative path - return ""; - } - - if (process.env.VERCEL_URL) { - // reference for vercel.com - return `https://${process.env.VERCEL_URL}`; - } - - // assume localhost - return `http://localhost:${process.env.PORT ?? 3000}`; -} +import { getBaseUrl } from "../utils"; export const trpc = createTRPCProxyClient({ transformer: superjson, diff --git a/apps/dashboard/lib/utils.ts b/apps/dashboard/lib/utils.ts index 3ab78de23b..5462a103b9 100644 --- a/apps/dashboard/lib/utils.ts +++ b/apps/dashboard/lib/utils.ts @@ -168,3 +168,18 @@ export const processTimeFilters = (date?: Date, newTime?: TimeUnit) => { const now = new Date(); return now; }; + +export function getBaseUrl() { + if (typeof window !== "undefined") { + // browser should use relative path + return ""; + } + + if (process.env.VERCEL_URL) { + // reference for vercel.com + return `https://${process.env.VERCEL_URL}`; + } + + // assume localhost + return `http://localhost:${process.env.PORT ?? 3000}`; +} \ No newline at end of file From 714454efb36ea4208e139f71e666dce06ab4debd Mon Sep 17 00:00:00 2001 From: Meg Stepp Date: Mon, 31 Mar 2025 13:25:53 -0400 Subject: [PATCH 2/6] actually use the redirect url --- apps/dashboard/app/auth/sign-in/oauth-signin.tsx | 3 +++ apps/dashboard/app/auth/sign-up/oauth-signup.tsx | 3 +++ 2 files changed, 6 insertions(+) diff --git a/apps/dashboard/app/auth/sign-in/oauth-signin.tsx b/apps/dashboard/app/auth/sign-in/oauth-signin.tsx index f05574538a..d97261d945 100644 --- a/apps/dashboard/app/auth/sign-in/oauth-signin.tsx +++ b/apps/dashboard/app/auth/sign-in/oauth-signin.tsx @@ -9,6 +9,7 @@ import * as React from "react"; import { signInViaOAuth } from "../actions"; import { OAuthButton } from "../oauth-button"; import { LastUsed, useLastUsed } from "./last_used"; +import { getBaseUrl } from "@/lib/utils"; export const OAuthSignIn: React.FC = () => { const [isLoading, setIsLoading] = React.useState(null); @@ -16,6 +17,7 @@ export const OAuthSignIn: React.FC = () => { const [clientReady, setClientReady] = React.useState(false); const searchParams = useSearchParams(); const redirectUrlComplete = searchParams?.get("redirect") ?? "/apis"; + const baseUrl = getBaseUrl(); // Set clientReady to true after hydration is complete React.useEffect(() => { @@ -28,6 +30,7 @@ export const OAuthSignIn: React.FC = () => { setLastUsed(provider); const url = await signInViaOAuth({ + redirectUrl: baseUrl, provider, redirectUrlComplete, }); diff --git a/apps/dashboard/app/auth/sign-up/oauth-signup.tsx b/apps/dashboard/app/auth/sign-up/oauth-signup.tsx index 47b3a250d7..8acd81443c 100644 --- a/apps/dashboard/app/auth/sign-up/oauth-signup.tsx +++ b/apps/dashboard/app/auth/sign-up/oauth-signup.tsx @@ -7,11 +7,13 @@ import type { OAuthStrategy } from "@/lib/auth/types"; import * as React from "react"; import { signInViaOAuth } from "../actions"; import { OAuthButton } from "../oauth-button"; +import { getBaseUrl } from "@/lib/utils"; export function OAuthSignUp() { const [isLoading, setIsLoading] = React.useState(null); const [clientReady, setClientReady] = React.useState(false); const redirectUrlComplete = "/new"; + const baseUrl = getBaseUrl(); // Set clientReady to true after hydration is complete React.useEffect(() => { @@ -22,6 +24,7 @@ export function OAuthSignUp() { try { setIsLoading(provider); const url = await signInViaOAuth({ + redirectUrl: baseUrl, provider, redirectUrlComplete, }); From 215e1b2643fd2f3c11e443187b5b35c67265fef0 Mon Sep 17 00:00:00 2001 From: James Perkins Date: Mon, 31 Mar 2025 13:55:15 -0400 Subject: [PATCH 3/6] Fix redirect urls --- .../app/auth/sign-in/oauth-signin.tsx | 4 --- .../app/auth/sign-up/oauth-signup.tsx | 3 +-- apps/dashboard/lib/auth/types.ts | 1 - apps/dashboard/lib/auth/workos.ts | 27 ++++++++++++++----- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/apps/dashboard/app/auth/sign-in/oauth-signin.tsx b/apps/dashboard/app/auth/sign-in/oauth-signin.tsx index d97261d945..a0f5df9fef 100644 --- a/apps/dashboard/app/auth/sign-in/oauth-signin.tsx +++ b/apps/dashboard/app/auth/sign-in/oauth-signin.tsx @@ -9,7 +9,6 @@ import * as React from "react"; import { signInViaOAuth } from "../actions"; import { OAuthButton } from "../oauth-button"; import { LastUsed, useLastUsed } from "./last_used"; -import { getBaseUrl } from "@/lib/utils"; export const OAuthSignIn: React.FC = () => { const [isLoading, setIsLoading] = React.useState(null); @@ -17,7 +16,6 @@ export const OAuthSignIn: React.FC = () => { const [clientReady, setClientReady] = React.useState(false); const searchParams = useSearchParams(); const redirectUrlComplete = searchParams?.get("redirect") ?? "/apis"; - const baseUrl = getBaseUrl(); // Set clientReady to true after hydration is complete React.useEffect(() => { @@ -28,9 +26,7 @@ export const OAuthSignIn: React.FC = () => { try { setIsLoading(provider); setLastUsed(provider); - const url = await signInViaOAuth({ - redirectUrl: baseUrl, provider, redirectUrlComplete, }); diff --git a/apps/dashboard/app/auth/sign-up/oauth-signup.tsx b/apps/dashboard/app/auth/sign-up/oauth-signup.tsx index 8acd81443c..46b3fb0e03 100644 --- a/apps/dashboard/app/auth/sign-up/oauth-signup.tsx +++ b/apps/dashboard/app/auth/sign-up/oauth-signup.tsx @@ -4,10 +4,10 @@ import { Loading } from "@/components/dashboard/loading"; import { GitHub, Google } from "@/components/ui/icons"; import { toast } from "@/components/ui/toaster"; import type { OAuthStrategy } from "@/lib/auth/types"; +import { getBaseUrl } from "@/lib/utils"; import * as React from "react"; import { signInViaOAuth } from "../actions"; import { OAuthButton } from "../oauth-button"; -import { getBaseUrl } from "@/lib/utils"; export function OAuthSignUp() { const [isLoading, setIsLoading] = React.useState(null); @@ -24,7 +24,6 @@ export function OAuthSignUp() { try { setIsLoading(provider); const url = await signInViaOAuth({ - redirectUrl: baseUrl, provider, redirectUrlComplete, }); diff --git a/apps/dashboard/lib/auth/types.ts b/apps/dashboard/lib/auth/types.ts index f4a2379e4d..687fc1793a 100644 --- a/apps/dashboard/lib/auth/types.ts +++ b/apps/dashboard/lib/auth/types.ts @@ -118,7 +118,6 @@ export interface SessionData { export type OAuthStrategy = "google" | "github"; export interface SignInViaOAuthOptions { - redirectUrl?: string; redirectUrlComplete: string; provider: OAuthStrategy; } diff --git a/apps/dashboard/lib/auth/workos.ts b/apps/dashboard/lib/auth/workos.ts index fbb362112b..7242184da4 100644 --- a/apps/dashboard/lib/auth/workos.ts +++ b/apps/dashboard/lib/auth/workos.ts @@ -4,6 +4,7 @@ import { type Invitation as WorkOSInvitation, type Organization as WorkOSOrganization, } from "@workos-inc/node"; +import { getBaseUrl } from "../utils"; import { BaseAuthProvider } from "./base-provider"; import { getCookie } from "./cookies"; import { @@ -192,7 +193,10 @@ export class WorkOSAuthProvider extends BaseAuthProvider { } // Organization Management - async createTenant(params: { name: string; userId: string }): Promise { + async createTenant(params: { + name: string; + userId: string; + }): Promise { const { name, userId } = params; if (!name || !userId) { throw new Error("Organization name and userId are required."); @@ -220,7 +224,9 @@ export class WorkOSAuthProvider extends BaseAuthProvider { } try { - const org = await this.provider.organizations.createOrganization({ name }); + const org = await this.provider.organizations.createOrganization({ + name, + }); return this.transformOrganizationData(org); } catch (error) { throw this.handleError(error); @@ -453,7 +459,10 @@ export class WorkOSAuthProvider extends BaseAuthProvider { inviterUserId: user.id, }); - return this.transformInvitationData(invitation, { orgId, inviterId: user.id }); + return this.transformInvitationData(invitation, { + orgId, + inviterId: user.id, + }); } catch (error) { throw this.handleError(error); } @@ -643,7 +652,10 @@ export class WorkOSAuthProvider extends BaseAuthProvider { } } - async verifyEmail(params: { code: string; token: string }): Promise { + async verifyEmail(params: { + code: string; + token: string; + }): Promise { const { code, token } = params; try { @@ -767,12 +779,13 @@ export class WorkOSAuthProvider extends BaseAuthProvider { // OAuth Methods signInViaOAuth(options: SignInViaOAuthOptions): string { - const { redirectUrl, provider, redirectUrlComplete } = options; + const { provider, redirectUrlComplete } = options; const state = encodeURIComponent(JSON.stringify({ redirectUrlComplete })); - + const baseUrl = getBaseUrl(); + const redirect = `${baseUrl}/auth/sso-callback`; return this.provider.userManagement.getAuthorizationUrl({ clientId: this.clientId, - redirectUri: redirectUrl ?? env().NEXT_PUBLIC_WORKOS_REDIRECT_URI, + redirectUri: redirect ?? env().NEXT_PUBLIC_WORKOS_REDIRECT_URI, provider: provider === "github" ? "GitHubOAuth" : "GoogleOAuth", state, }); From 7f84cbce4b145865f3a027edea3afbff8750b575 Mon Sep 17 00:00:00 2001 From: James Perkins Date: Mon, 31 Mar 2025 13:59:31 -0400 Subject: [PATCH 4/6] missed getBase --- apps/dashboard/app/auth/sign-up/oauth-signup.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/dashboard/app/auth/sign-up/oauth-signup.tsx b/apps/dashboard/app/auth/sign-up/oauth-signup.tsx index 46b3fb0e03..c5f0c55e78 100644 --- a/apps/dashboard/app/auth/sign-up/oauth-signup.tsx +++ b/apps/dashboard/app/auth/sign-up/oauth-signup.tsx @@ -13,7 +13,6 @@ export function OAuthSignUp() { const [isLoading, setIsLoading] = React.useState(null); const [clientReady, setClientReady] = React.useState(false); const redirectUrlComplete = "/new"; - const baseUrl = getBaseUrl(); // Set clientReady to true after hydration is complete React.useEffect(() => { From b42f777f8ff2485e5ad04de968062862afa0dacb Mon Sep 17 00:00:00 2001 From: James Perkins Date: Mon, 31 Mar 2025 13:59:53 -0400 Subject: [PATCH 5/6] missed import --- apps/dashboard/app/auth/sign-up/oauth-signup.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/dashboard/app/auth/sign-up/oauth-signup.tsx b/apps/dashboard/app/auth/sign-up/oauth-signup.tsx index c5f0c55e78..47b3a250d7 100644 --- a/apps/dashboard/app/auth/sign-up/oauth-signup.tsx +++ b/apps/dashboard/app/auth/sign-up/oauth-signup.tsx @@ -4,7 +4,6 @@ import { Loading } from "@/components/dashboard/loading"; import { GitHub, Google } from "@/components/ui/icons"; import { toast } from "@/components/ui/toaster"; import type { OAuthStrategy } from "@/lib/auth/types"; -import { getBaseUrl } from "@/lib/utils"; import * as React from "react"; import { signInViaOAuth } from "../actions"; import { OAuthButton } from "../oauth-button"; From 84b609456be37092c6c5b764b0828c8a7fa035b5 Mon Sep 17 00:00:00 2001 From: James Perkins Date: Mon, 31 Mar 2025 14:08:57 -0400 Subject: [PATCH 6/6] Swap these I don't trust Vercel --- apps/dashboard/lib/auth/workos.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/dashboard/lib/auth/workos.ts b/apps/dashboard/lib/auth/workos.ts index 7242184da4..b0138c2cb4 100644 --- a/apps/dashboard/lib/auth/workos.ts +++ b/apps/dashboard/lib/auth/workos.ts @@ -785,7 +785,7 @@ export class WorkOSAuthProvider extends BaseAuthProvider { const redirect = `${baseUrl}/auth/sso-callback`; return this.provider.userManagement.getAuthorizationUrl({ clientId: this.clientId, - redirectUri: redirect ?? env().NEXT_PUBLIC_WORKOS_REDIRECT_URI, + redirectUri: env().NEXT_PUBLIC_WORKOS_REDIRECT_URI ?? redirect, provider: provider === "github" ? "GitHubOAuth" : "GoogleOAuth", state, });