Skip to content

Commit

Permalink
Merge pull request #280 from briefercloud/fix-auth-redirects
Browse files Browse the repository at this point in the history
fix auth redirects
  • Loading branch information
vieiralucas authored Dec 16, 2024
2 parents 40c66ee + d35f958 commit 3dee407
Show file tree
Hide file tree
Showing 52 changed files with 401 additions and 469 deletions.
26 changes: 13 additions & 13 deletions apps/api/src/auth/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { obscureEmail } from '../emails.js'
import { comparePassword, hashPassword, isValidPassword } from '../password.js'
import properties from '../properties.js'
import { IOServer } from '../websocket/index.js'
import { callbackUrlSchema, cookieOptions } from './index.js'
import { cookieOptions } from './index.js'
import {
authenticationMiddleware,
createAuthToken,
Expand Down Expand Up @@ -137,14 +137,18 @@ export default function getRouter<H extends ApiUser>(

router.post('/sign-in/password', async (req, res) => {
const payload = z
.object({ email: z.string().trim().email(), password: z.string() })
.object({
email: z.string().trim().email(),
password: z.string(),
callback: z.string().optional(),
})
.safeParse(req.body)
if (!payload.success) {
res.status(400).end()
return
}

const { email, password } = payload.data
const { email, password, callback } = payload.data

const user = await prisma().user.findUnique({
where: { email },
Expand All @@ -164,7 +168,10 @@ export default function getRouter<H extends ApiUser>(
return
}

const loginLink = createLoginLink(user.id, config.FRONTEND_URL)
const loginLink = createLoginLink(
user.id,
`${config.FRONTEND_URL}/${callback ?? ''}`
)

res.json({ email: obscureEmail(user.email), loginLink })
})
Expand All @@ -184,16 +191,9 @@ export default function getRouter<H extends ApiUser>(
})
})

router.get('/logout', authenticationMiddleware, async (req, res) => {
const query = z.object({ callback: callbackUrlSchema }).safeParse(req.query)
if (!query.success) {
res.status(400).end()
return
}

router.get('/logout', async (_req, res) => {
res.clearCookie('token')

res.redirect(query.data.callback)
res.redirect(config.FRONTEND_URL)
})

return router
Expand Down
7 changes: 0 additions & 7 deletions apps/api/src/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { z } from 'zod'
import { CookieOptions } from 'express'
import { config } from '../config/index.js'
import parseDuration from 'parse-duration'
Expand All @@ -7,12 +6,6 @@ import getBaseRouter from './base.js'

const JWT_EXPIRATION_MS = parseDuration(config().AUTH_JWT_EXPIRATION)

export const callbackUrlSchema = z
.string()
.trim()
.url()
.refine((url) => new URL(url).origin === config().FRONTEND_URL)

export const cookieOptions: CookieOptions = {
httpOnly: true,
secure: config().NODE_ENV === 'production' && !config().ALLOW_HTTP,
Expand Down
6 changes: 3 additions & 3 deletions apps/api/src/auth/token.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export async function authenticationMiddleware(
try {
const session = await sessionFromCookies(req.cookies)
if (!session) {
res.status(401).end()
res.status(401).json('Unauthorized')
return
}

Expand All @@ -156,12 +156,12 @@ export async function authenticationMiddleware(
next()
} catch (err) {
if (err instanceof jwt.JsonWebTokenError) {
res.status(403).send('Invalid token')
res.status(403).json('Forbidden')
return
}

req.log.error({ err }, 'Error verifying token')
res.status(500).end()
res.status(500).json('Internal server error')
}
}

Expand Down
11 changes: 9 additions & 2 deletions apps/api/src/websocket/workspace/documents.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
import { getDocument, listDocuments } from '@briefer/database'
import { createDocument, getDocument, listDocuments } from '@briefer/database'
import { IOServer, Socket } from '../index.js'

export async function emitDocuments(socket: Socket, workspaceId: string) {
const documents = await listDocuments(workspaceId)
let documents = await listDocuments(workspaceId)
if (documents.length === 0) {
await createDocument(workspaceId, {
orderIndex: 0,
version: 2,
})
documents = await listDocuments(workspaceId)
}

socket.emit('workspace-documents', { workspaceId, documents })
}
Expand Down
9 changes: 0 additions & 9 deletions apps/web/src/components/BannedPage.tsx

This file was deleted.

2 changes: 1 addition & 1 deletion apps/web/src/components/Comments.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ interface Props {
onHide: () => void
}
export default function Comments(props: Props) {
const session = useSession()
const session = useSession({ redirectToLogin: true })
const [comments, { createComment }] = useComments(props.documentId)
const [content, setContent] = useState('')

Expand Down
22 changes: 11 additions & 11 deletions apps/web/src/components/Dashboard/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as Y from 'yjs'
import { useCallback, useEffect, useMemo, useState } from 'react'
import { useCallback, useMemo, useState } from 'react'
import { SquaresPlusIcon } from '@heroicons/react/24/solid'
import { BookUpIcon } from 'lucide-react'
import { EyeIcon } from '@heroicons/react/24/outline'
Expand Down Expand Up @@ -28,12 +28,12 @@ import Files from '../Files'
import { PublishBlinkingSignal } from '../BlinkingSignal'
import { Tooltip } from '../Tooltips'
import { NEXT_PUBLIC_PUBLIC_URL } from '@/utils/env'
import useWebsocket from '@/hooks/useWebsocket'
import { SQLExtensionProvider } from '../v2Editor/CodeEditor/sql'
import { SessionUser } from '@/hooks/useAuth'

interface Props {
document: ApiDocument
userId: string
user: SessionUser
role: UserWorkspaceRole
isEditing: boolean
publish: () => Promise<void>
Expand All @@ -45,20 +45,20 @@ export default function Dashboard(props: Props) {
return props.document.clock
}

return props.document.userAppClock[props.userId] ?? props.document.appClock
return props.document.userAppClock[props.user.id] ?? props.document.appClock
}, [
props.isEditing,
props.document.clock,
props.document.userAppClock,
props.userId,
props.user,
])

const { yDoc, syncing, isDirty } = useYDoc(
props.document.workspaceId,
props.document.id,
!props.isEditing,
clock,
props.userId,
props.user.id,
props.document.publishedAt,
true,
null
Expand All @@ -74,7 +74,6 @@ export default function Dashboard(props: Props) {
const aiTasks = useMemo(() => AITasks.fromYjs(yDoc), [yDoc])

const router = useRouter()
const socket = useWebsocket()

const onPublish = useCallback(async () => {
if (props.publishing) {
Expand Down Expand Up @@ -232,6 +231,7 @@ export default function Dashboard(props: Props) {
<Layout
topBarClassname={!props.isEditing ? 'bg-gray-50' : undefined}
topBarContent={topBarContent}
user={props.user}
>
<div className="w-full flex relative subpixel-antialiased bg-dashboard-gray">
<div className="w-full flex flex-col relative">
Expand Down Expand Up @@ -264,7 +264,7 @@ export default function Dashboard(props: Props) {
disabled={false}
yDoc={yDoc}
primary={true}
userId={props.userId}
userId={props.user.id}
executionQueue={executionQueue}
/>
)}
Expand Down Expand Up @@ -296,7 +296,7 @@ export default function Dashboard(props: Props) {
workspaceId={props.document.workspaceId}
visible={selectedSidebar === 'files'}
onHide={onHideSidebar}
userId={props.userId}
userId={props.user.id}
yDoc={yDoc}
executionQueue={executionQueue}
/>
Expand Down Expand Up @@ -348,7 +348,7 @@ function DashboardContent(
latestBlockId={latestBlockId}
isEditing={props.isEditing}
userRole={props.role}
userId={props.userId}
userId={props.user.id}
executionQueue={props.executionQueue}
aiTasks={props.aiTasks}
/>
Expand All @@ -359,7 +359,7 @@ function DashboardContent(
yDoc={props.yDoc}
onDragStart={onDragStart}
onAddBlock={onAddBlock}
userId={props.userId}
userId={props.user.id}
executionQueue={props.executionQueue}
aiTasks={props.aiTasks}
/>
Expand Down
45 changes: 11 additions & 34 deletions apps/web/src/components/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,17 +30,14 @@ import { useRouter } from 'next/router'
import { Page } from '@/components/PagePath'
import { useDocuments } from '@/hooks/useDocuments'
import { useFavorites } from '@/hooks/useFavorites'
import { useWorkspaces } from '@/hooks/useWorkspaces'
import { useStringQuery } from '@/hooks/useQueryArgs'
import { useSession, useSignout } from '@/hooks/useAuth'
import { SessionUser, useSession, useSignout } from '@/hooks/useAuth'
import { CpuChipIcon } from '@heroicons/react/24/solid'
import type { UserWorkspaceRole } from '@briefer/database'
import ReactDOM from 'react-dom'
import useDropdownPosition from '@/hooks/dropdownPosition'
import DocumentTree from './DocumentsTree'
import useSideBar from '@/hooks/useSideBar'
import { isBanned } from '@/utils/isBanned'
import BannedPage from './BannedPage'
import { SubscriptionBadge } from './SubscriptionBadge'
import MobileWarning from './MobileWarning'
import { DataSourceBlinkingSignal } from './BlinkingSignal'
Expand Down Expand Up @@ -109,17 +106,17 @@ interface Props {
topBarClassname?: string
topBarContent?: React.ReactNode
hideOnboarding?: boolean
user: SessionUser
}

export default function Layout({
children,
pagePath,
topBarClassname,
topBarContent,
user,
hideOnboarding,
}: Props) {
const session = useSession()

const [isSearchOpen, setSearchOpen] = useState(false)
useHotkeys(['mod+k'], () => {
setSearchOpen((prev) => !prev)
Expand All @@ -137,21 +134,6 @@ export default function Layout({
const workspaceId = useStringQuery('workspaceId')
const documentId = useStringQuery('documentId')

const [{ data: workspaces, isLoading: isLoadingWorkspaces }] = useWorkspaces()

const signOut = useSignout()
useEffect(() => {
const workspace = workspaces.find((w) => w.id === workspaceId)

if (!workspace && !isLoadingWorkspaces) {
if (workspaces.length > 0) {
router.replace(`/workspaces/${workspaces[0].id}/documents`)
} else {
signOut()
}
}
}, [workspaces, isLoadingWorkspaces, signOut])

const [
documentsState,
{
Expand Down Expand Up @@ -270,14 +252,14 @@ export default function Layout({
return false
}

const role = session.data?.roles[workspaceId]
const role = user.roles[workspaceId]
if (!role) {
return false
}

return item.allowedRoles.has(role)
},
[session.data, workspaceId]
[user, workspaceId]
)

const scrollRef = useRef<HTMLDivElement>(null)
Expand All @@ -303,11 +285,6 @@ export default function Layout({
}
}, [workspaceId, scrollRef])

const userEmail = session.data?.email
if (userEmail && isBanned(userEmail)) {
return <BannedPage />
}

return (
<div className={`flex w-full h-full ${syne.className}`}>
<MobileWarning />
Expand Down Expand Up @@ -348,7 +325,7 @@ export default function Layout({
onFavorite={onFavoriteDocument}
onUnfavorite={onUnfavoriteDocument}
onSetIcon={onSetIcon}
role={session.data?.roles[workspaceId] ?? 'viewer'}
role={user.roles[workspaceId] ?? 'viewer'}
flat={true}
onCreate={onCreateDocument}
onUpdateParent={onUpdateDocumentParent}
Expand Down Expand Up @@ -376,7 +353,7 @@ export default function Layout({
/>
</button>

{session.data?.roles[workspaceId] !== 'viewer' && (
{user.roles[workspaceId] !== 'viewer' && (
<button
onClick={onCreateDocumentHandler}
className="p-1 hover:text-ceramic-500 hover:bg-ceramic-100/70 rounded-md hover:cursor-pointer"
Expand All @@ -396,7 +373,7 @@ export default function Layout({
onFavorite={onFavoriteDocument}
onUnfavorite={onUnfavoriteDocument}
onSetIcon={onSetIcon}
role={session.data?.roles[workspaceId] ?? 'viewer'}
role={user.roles[workspaceId] ?? 'viewer'}
onCreate={onCreateDocument}
onUpdateParent={onUpdateDocumentParent}
/>
Expand Down Expand Up @@ -443,18 +420,18 @@ export default function Layout({
className="text-gray-500 hover:bg-ceramic-100/80 group text-sm font-medium leading-6 w-full flex py-1 rounded-sm hover:text-ceramic-600"
>
<div className="w-full flex items-center gap-x-2 px-4">
{session.data?.picture ? (
{user.picture ? (
<img
className="h-4 w-4 rounded-full"
src={session.data.picture}
src={user.picture}
referrerPolicy="no-referrer"
/>
) : (
<UserIcon className="h-4 w-4 rounded-full" />
)}
<span className="sr-only">Your profile</span>
<span aria-hidden="true" className="mt-0.5">
{session.data?.name}
{user.name}
</span>
</div>
<UserDropdown workspaceId={workspaceId} />
Expand Down
Loading

0 comments on commit 3dee407

Please sign in to comment.