Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

clients/web: magic link middleware #1917

Merged
merged 1 commit into from
Dec 11, 2023
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: 1 addition & 0 deletions clients/apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@tanstack/react-query-next-experimental": "^5.0.5",
"@tanstack/react-query-persist-client": "^5.0.5",
"@ts-stack/markdown": "^1.4.0",
"@types/web": "^0.0.125",
"@vercel/blob": "^0.14.1",
"@vercel/og": "^0.5.7",
"class-variance-authority": "^0.6.0",
Expand Down
23 changes: 20 additions & 3 deletions clients/apps/web/src/app/(email)/email/article/[id]/route.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,8 @@ export async function GET(
params: { id: string }
},
): Promise<NextResponse> {
const { searchParams } = new URL(req.url)

let article: Article

try {
Expand All @@ -195,6 +197,15 @@ export async function GET(
? new Date(article.published_at)
: new Date()

const preAuthLink = (url: string): string => {
const u = new URL(url)
const injectMagicLinkToken = searchParams.get('inject_magic_link_token')
if (injectMagicLinkToken) {
u.searchParams.set('magic_link_token', injectMagicLinkToken)
}
return u.toString()
}

const html = render(
<Html lang="en">
<Tailwind config={twConfig}>
Expand All @@ -206,7 +217,9 @@ export async function GET(
<Section className="bg-gray-100 p-4">
<center>
<a
href={`https://polar.sh/${post.organization.name}/posts/${post.slug}`}
href={preAuthLink(
`https://polar.sh/${post.organization.name}/posts/${post.slug}`,
)}
target="_blank"
className="text-sm text-black"
>
Expand All @@ -220,7 +233,9 @@ export async function GET(
<Column>
<h1>
<a
href={`https://polar.sh/${post.organization.name}/posts/${post.slug}`}
href={preAuthLink(
`https://polar.sh/${post.organization.name}/posts/${post.slug}`,
)}
target="_blank"
className="text-gray-900 no-underline"
>
Expand Down Expand Up @@ -269,7 +284,9 @@ export async function GET(
<center className="py-6 text-xs text-gray-500">
You received this email because you&apos;re a subscriber to{' '}
<a
href={`https://polar.sh/${post.organization.name}`}
href={preAuthLink(
`https://polar.sh/${post.organization.name}`,
)}
target="_blank"
className="!underline underline-offset-1"
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ export default async function Page({
searchParams,
}: {
params: { organization: string; postSlug: string }
searchParams: { [key: string]: string | string[] | undefined }
searchParams: {
tab?: string
}
}) {
const api = getServerSideAPI()

Expand Down Expand Up @@ -137,7 +139,7 @@ export default async function Page({
)
} catch (err) {}

const currentTab = searchParams.tab as string | undefined
const currentTab = searchParams.tab

return (
<>
Expand Down
4 changes: 3 additions & 1 deletion clients/apps/web/src/components/Feed/Posts/BrowserEmbed.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ const EmbedIssue = (props: { src: string }) => {
}
}

get()
if (!realIssue) {
get()
}

return () => {
active = false
Expand Down
61 changes: 61 additions & 0 deletions clients/apps/web/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { getServerSideAPI } from '@/utils/api'
import { NextRequest, NextResponse } from 'next/server'

// exchange the magic link with the server
const handleMagicLinkToken = async (magic: string): Promise<string[]> => {
const api = getServerSideAPI()

try {
const user = await api.users.getAuthenticated()
if (user.id) {
// user is already logged in
return []
}
} catch {}

try {
const magicLink = await api.magicLink.authenticateMagicLinkRaw({
token: magic,
})
const val = await magicLink.value()
if (val.success) {
return magicLink.raw.headers.getSetCookie()
}
} catch {}

return []
}

export async function middleware(request: NextRequest) {
if (request.nextUrl.pathname.startsWith('/_next/')) {
return
}

// Intercept all requests with magic_link_token set.
// Try to authenticate with this token, and redirect to the URL with the token stripped.
if (request.nextUrl.searchParams.has('magic_link_token')) {
const magic = request.nextUrl.searchParams.get('magic_link_token')

let setCookies: string[] = []

if (magic) {
setCookies = await handleMagicLinkToken(magic)
}

const target = new URL(request.nextUrl)

// remove magic_link_token from URL even if it was invalid
target.searchParams.delete('magic_link_token')

// redirect
const response = NextResponse.redirect(target)

// forward set-cookie header(s) to client
for (const setCookie of setCookies) {
response.headers.set('Set-Cookie', setCookie)
}
return response
}

return
}
7 changes: 7 additions & 0 deletions clients/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading