Skip to content

Commit

Permalink
fix auth redirects once for all
Browse files Browse the repository at this point in the history
  • Loading branch information
vieiralucas committed Dec 31, 2024
1 parent 0a587f0 commit 8b32045
Showing 1 changed file with 135 additions and 99 deletions.
234 changes: 135 additions & 99 deletions apps/api/src/auth/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,20 @@ import { isWorkspaceNameValid } from '../utils/validation.js'
import { captureWorkspaceCreated } from '../events/posthog.js'
import path from 'path'

function joinURL(base: string, ...parts: string[]) {
try {
const baseURL = new URL(base)
return (
baseURL.protocol +
'//' +
path.join(baseURL.host, baseURL.pathname, ...parts)
)
} catch {
// base might not be a valid URL, so just join the parts
return path.join(base, ...parts)
}
}

type BaseAuthConfig = {
FRONTEND_URL: string
}
Expand All @@ -29,69 +43,76 @@ export default function getRouter<H extends ApiUser>(
const router = Router({ mergeParams: true })

router.get('/link/callback', async (req, res) => {
const query = z.object({ t: z.string().min(1) }).safeParse(req.query)
if (!query.success) {
res.status(400).end()
return
}
try {
const query = z.object({ t: z.string().min(1) }).safeParse(req.query)
if (!query.success) {
res.status(400).end()
return
}

const token = query.data.t
const { data, isExpired } = decodeLoginToken(token)
if (!data) {
res.status(401).send('Invalid token')
return
}
const token = query.data.t
const { data, isExpired } = decodeLoginToken(token)
if (!data) {
res.status(401).send('Invalid token')
return
}

if (isExpired) {
res.redirect(
path.join(config.FRONTEND_URL, `/auth/expired-signin?t=${token}`)
)
return
}
if (isExpired) {
const redirect = joinURL(config.FRONTEND_URL, '/auth/expired-signin')
res.redirect(redirect)
return
}

await confirmUser(data.userId)
await confirmUser(data.userId)

res.cookie('token', createAuthToken(data.userId), cookieOptions)
res.redirect(data.callback)
res.cookie('token', createAuthToken(data.userId), cookieOptions)
res.redirect(data.callback)
} catch (err) {
req.log.error({ err }, 'Failed to handle link callback request')
res.sendStatus(500)
}
})

router.post('/sign-up/password', async (req, res) => {
const { needsSetup } = await properties()
try {
const { needsSetup } = await properties()

if (!needsSetup) {
res.status(400).json({
reason: 'setup-already-done',
})
return
}
if (!needsSetup) {
res.status(400).json({
reason: 'setup-already-done',
})
return
}

const payload = z
.object({
workspaceName: z.string(),
name: z.string().trim(),
email: z.string().trim().email(),
password: z.string(),
shareEmail: z.boolean(),
source: z.string().nullable(),
})
.safeParse(req.body)
const payload = z
.object({
workspaceName: z.string(),
name: z.string().trim(),
email: z.string().trim().email(),
password: z.string(),
shareEmail: z.boolean(),
source: z.string().nullable(),
})
.safeParse(req.body)

if (!payload.success || !isWorkspaceNameValid(payload.data.workspaceName)) {
res.status(400).json({
reason: 'invalid-payload',
})
return
}
if (
!payload.success ||
!isWorkspaceNameValid(payload.data.workspaceName)
) {
res.status(400).json({
reason: 'invalid-payload',
})
return
}

const { email, password, shareEmail, source } = payload.data
if (!isValidPassword(password)) {
res.status(400).json({
reason: 'invalid-password',
})
return
}
const { email, password, shareEmail, source } = payload.data
if (!isValidPassword(password)) {
res.status(400).json({
reason: 'invalid-password',
})
return
}

try {
const existingUser = await getUserByEmail(email)
if (existingUser) {
res.status(400).json({
Expand Down Expand Up @@ -139,64 +160,79 @@ 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(),
callback: z.string().optional(),
})
.safeParse(req.body)
if (!payload.success) {
res.status(400).end()
return
}
try {
const payload = z
.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, callback } = payload.data
const { email, password, callback } = payload.data

const user = await prisma().user.findUnique({
where: { email },
select: { id: true, email: true, passwordDigest: true },
})
if (!user || !user.passwordDigest) {
res.status(400).end()
return
}
const user = await prisma().user.findUnique({
where: { email },
select: { id: true, email: true, passwordDigest: true },
})
if (!user || !user.passwordDigest) {
res.status(400).end()
return
}

const validPassword = await comparePassword({
encrypted: user.passwordDigest,
password,
})
if (!validPassword) {
res.status(400).end()
return
}
const validPassword = await comparePassword({
encrypted: user.passwordDigest,
password,
})
if (!validPassword) {
res.status(400).end()
return
}

const loginLink = createLoginLink(
user.id,
path.join(config.FRONTEND_URL, callback ?? '')
)
const redirect = callback
? joinURL(config.FRONTEND_URL, callback)
: config.FRONTEND_URL
const loginLink = createLoginLink(user.id, redirect)

res.json({ email: obscureEmail(user.email), loginLink })
res.json({ email: obscureEmail(user.email), loginLink })
} catch (err) {
req.log.error({ err }, 'Failed to handle sign-in request')
res.sendStatus(500)
}
})

router.get('/session', authenticationMiddleware, async (req, res) => {
const userWorkspaces = await prisma().userWorkspace.findMany({
where: { userId: req.session.user.id },
})

const user = transformUserSession
? transformUserSession(req.session.user)
: req.session.user

res.json({
...user,
roles: fromPairs(userWorkspaces.map((uw) => [uw.workspaceId, uw.role])),
})
try {
const userWorkspaces = await prisma().userWorkspace.findMany({
where: { userId: req.session.user.id },
})

const user = transformUserSession
? transformUserSession(req.session.user)
: req.session.user

res.json({
...user,
roles: fromPairs(userWorkspaces.map((uw) => [uw.workspaceId, uw.role])),
})
} catch (err) {
req.log.error({ err }, 'Failed to handle session request')
res.sendStatus(500)
}
})

router.get('/logout', async (_req, res) => {
res.clearCookie('token')
res.redirect(config.FRONTEND_URL)
router.get('/logout', async (req, res) => {
try {
res.clearCookie('token')
res.redirect(config.FRONTEND_URL)
} catch (err) {
req.log.error({ err }, 'Failed to handle logout request')
res.sendStatus(500)
}
})

return router
Expand Down

0 comments on commit 8b32045

Please sign in to comment.