Skip to content

Commit

Permalink
feat(ui+engine): Add environment variable flag to disable authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
daryllimyt committed Apr 27, 2024
1 parent 3a58c5c commit 917499c
Show file tree
Hide file tree
Showing 10 changed files with 233 additions and 127 deletions.
4 changes: 3 additions & 1 deletion docker-compose.yaml
Expand Up @@ -23,6 +23,7 @@ services:
TRACECAT__RUNNER_URL: ${TRACECAT__RUNNER_URL}
# Auth
CLERK_FRONTEND_API_URL: ${CLERK_FRONTEND_API_URL}
TRACECAT__DISABLE_AUTH: ${TRACECAT__DISABLE_AUTH}
# Integrations
OPENAI_API_KEY: ${OPENAI_API_KEY}
restart: unless-stopped
Expand Down Expand Up @@ -115,10 +116,11 @@ services:
NEXT_PUBLIC_SUPABASE_ANON_KEY: ${NEXT_PUBLIC_SUPABASE_ANON_KEY}
NEXT_PUBLIC_SUPABASE_URL: ${NEXT_PUBLIC_SUPABASE_URL}
# Auth
NEXT_PUBLIC_DISABLE_AUTH: ${TRACECAT__DISABLE_AUTH} # Prefix with NEXT_PUBLIC_ to expose to client
CLERK_SECRET_KEY: ${CLERK_SECRET_KEY}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY}
NEXT_PUBLIC_CLERK_SIGN_IN_URL: ${NEXT_PUBLIC_CLERK_SIGN_IN_URL}
NEXT_PUBLIC_CLERK_SIGN_OUT_URL: ${NEXT_PUBLIC_CLERK_SIGN_OUT_URL}
NEXT_PUBLIC_CLERK_SIGN_UP_URL: ${NEXT_PUBLIC_CLERK_SIGN_UP_URL}
restart: unless-stopped
depends_on:
- api
Expand Down
24 changes: 16 additions & 8 deletions frontend/src/app/settings/account/page.tsx
@@ -1,5 +1,6 @@
import { SignedIn, SignedOut, SignInButton, UserButton } from "@clerk/nextjs"

import { authConfig } from "@/config/auth"
import { Separator } from "@/components/ui/separator"

export default function Page() {
Expand All @@ -10,14 +11,21 @@ export default function Page() {
</div>
<Separator />
<div className="space-y-4">
<div className="text-sm">
<div className="flex items-center justify-between">
<h6 className="font-bold">Clerk Settings</h6>
<ClerkUserButton />
</div>
<p className="text-muted-foreground">
Please click on the user icon on the right to access Clerk settings.
</p>
<div className="space-y-2 text-sm">
<h6 className="font-bold">Clerk Settings</h6>
{authConfig.disabled ? (
<div className="text-sm text-muted-foreground">
Authentication is disabled.
</div>
) : (
<div className="flex items-center justify-between">
<p className="text-muted-foreground">
Please click on the user icon on the right to access Clerk
settings. t{" "}
</p>
<ClerkUserButton />
</div>
)}
</div>
</div>
</div>
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/app/sign-in/[[...sign-in]]/page.tsx
@@ -1,6 +1,12 @@
import { SignIn } from "@clerk/nextjs"

import { authConfig } from "@/config/auth"
import { AuthDisabled } from "@/components/auth-disabled"

export default function Page() {
if (authConfig.disabled) {
return <AuthDisabled />
}
return (
<div className="flex h-full w-full items-center justify-center">
<SignIn
Expand Down
6 changes: 6 additions & 0 deletions frontend/src/app/sign-up/[[...sign-up]]/page.tsx
@@ -1,6 +1,12 @@
import { SignUp } from "@clerk/nextjs"

import { authConfig } from "@/config/auth"
import { AuthDisabled } from "@/components/auth-disabled"

export default function Page() {
if (authConfig.disabled) {
return <AuthDisabled />
}
return (
<div className="flex h-full w-full items-center justify-center">
<SignUp
Expand Down
39 changes: 39 additions & 0 deletions frontend/src/components/auth-disabled.tsx
@@ -0,0 +1,39 @@
"use client"

import { useState } from "react"
import Image from "next/image"
import { useRouter } from "next/navigation"
import TracecatIcon from "public/icon.png"

import { newUserFlow } from "@/lib/onboarding"
import { Button } from "@/components/ui/button"
import { Icons } from "@/components/icons"

/**
* This component is displayed when the authentication is disabled.
* @returns AuthDisabled component
*/
export function AuthDisabled() {
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const handleClick = async () => {
setIsLoading(true)
await newUserFlow()
router.push("/workflows")
}
return (
<div className="flex h-full w-full flex-col items-center justify-center space-y-4">
<Image src={TracecatIcon} alt="Tracecat" className="mx-auto h-16 w-16" />
<h1 className="text-lg font-bold">Proceed to Tracecat Cloud</h1>
<p className="max-w-[30vw] text-center text-sm text-muted-foreground">
Authentication is disabled. You can activate this by setting the
TRACECAT__DISABLE_AUTH environment variable and linking your Clerk
account.
</p>
<Button className="text-xs" onClick={handleClick} disabled={isLoading}>
{isLoading && <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />}
Continue to dashboard
</Button>
</div>
)
}
8 changes: 7 additions & 1 deletion frontend/src/components/nav/user-nav.tsx
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation"
import { useClerk, useUser } from "@clerk/nextjs"
import { BookText, KeyRound, LogOut, Settings, UsersRound } from "lucide-react"

import { authConfig } from "@/config/auth"
import { siteConfig } from "@/config/site"
import { userDefaults } from "@/config/user"
import { Button } from "@/components/ui/button"
Expand All @@ -24,7 +25,12 @@ export default function UserNav() {
const { user } = useUser()
const { signOut } = useClerk()
const router = useRouter()
const handleSignOut = () => signOut(() => router.push("/"))
const handleSignOut = () => {
if (authConfig.disabled) {
return router.push("/")
}
return signOut(() => router.push("/"))
}
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
Expand Down
5 changes: 5 additions & 0 deletions frontend/src/config/auth.ts
@@ -0,0 +1,5 @@
export const authConfig = {
disabled: ["1", "true"].includes(
process.env.NEXT_PUBLIC_DISABLE_AUTH || "false"
),
}
83 changes: 40 additions & 43 deletions frontend/src/lib/api.ts
Expand Up @@ -3,13 +3,11 @@ import { Clerk } from "@clerk/clerk-js"
import { auth } from "@clerk/nextjs/server"
import axios, { type InternalAxiosRequestConfig } from "axios"

import { authConfig } from "@/config/auth"
import { isServer } from "@/lib/utils"

// Determine the base URL based on the execution environment
let baseURL = process.env.NEXT_PUBLIC_API_URL
export const IS_AUTH_DISABLED: boolean = ["1", "true"].includes(
process.env.DISABLE_AUTH || "false"
)

// Use different base url for server-side
if (process.env.NODE_ENV === "development" && isServer()) {
Expand All @@ -21,36 +19,52 @@ export const client = axios.create({
})
const __clerk = new Clerk(process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY!)

client.interceptors.request.use(async (config: InternalAxiosRequestConfig) => {
const token = await getAuthToken()
config.headers["Authorization"] = `Bearer ${token}`
return config
})

export type Client = typeof client

/**
* Gets the auth token, or redirects to the login page
*
* @returns The authentication token
*
*/
export async function getAuthToken() {
if (authConfig.disabled) {
console.warn("Auth is disabled, using test token.")
return "super-secret-token-32-characters-long"
}
let token: string | null | undefined
if (isServer()) {
return await auth().getToken()
token = await auth().getToken()
} else {
await __clerk.load()
token = await __clerk.session?.getToken()
}

await __clerk.load()
return await __clerk.session?.getToken()
if (!token) {
console.error("Failed to get authenticated client, redirecting to login")
return redirect("/")
}
return token
}

if (IS_AUTH_DISABLED) {
console.log(
"Running with `DISABLE_AUTH` enabled, Axios client will not use authenticated interceptor."
)
} else {
console.log("Configuring Axios client with authenticated interceptor")
client.interceptors.request.use(
async (config: InternalAxiosRequestConfig) => {
const token = await getAuthToken()
if (!token) {
console.error("Failed to get token, redirecting to login")
return redirect("/")
}
config.headers["Authorization"] = `Bearer ${token}`
return config
}
)
export async function authFetch(input: RequestInfo, init?: RequestInit) {
const token = await getAuthToken()
const { headers, ...rest } = init ?? {}
const enhancedInit = {
...rest,
headers: {
...headers,
Authorization: `Bearer ${token}`,
},
}
return await fetch(input, enhancedInit)
}

export type Client = typeof client

export async function streamingResponse(endpoint: string, init?: RequestInit) {
return fetch(`${process.env.NEXT_PUBLIC_API_URL}${endpoint}`, init)
}
Expand Down Expand Up @@ -110,20 +124,3 @@ export async function* streamGenerator(
reader.releaseLock()
}
}

export async function authFetch(input: RequestInfo, init?: RequestInit) {
const token = await getAuthToken()
if (!token) {
console.error("Failed to get authenticated client, redirecting to login")
return redirect("/")
}
const { headers, ...rest } = init ?? {}
const enhancedInit = {
...rest,
headers: {
...headers,
Authorization: `Bearer ${token}`,
},
}
return await fetch(input, enhancedInit)
}
6 changes: 6 additions & 0 deletions frontend/src/middleware.ts
Expand Up @@ -6,6 +6,8 @@ import {
} from "@clerk/nextjs/server"
import { get } from "@vercel/edge-config"

import { authConfig } from "@/config/auth"

const isProtectedRoute = createRouteMatcher([
"/workflows(.*)",
"/settings(.*)",
Expand All @@ -14,6 +16,10 @@ const isProtectedRoute = createRouteMatcher([
])
export default clerkMiddleware(
async (auth: ClerkMiddlewareAuth, req: NextRequest) => {
if (authConfig.disabled) {
console.warn("Auth disabled, skipping authentication middleware")
return NextResponse.next()
}
// ** Site down **
if (
process.env.NEXT_PUBLIC_APP_ENV === "production" &&
Expand Down

0 comments on commit 917499c

Please sign in to comment.