Skip to content

Commit

Permalink
clients/article: pre-authenticated links in emails
Browse files Browse the repository at this point in the history
  • Loading branch information
zegl committed Dec 8, 2023
1 parent 2aa2d0c commit 7d20642
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 7 deletions.
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.

2 changes: 1 addition & 1 deletion server/polar/magic_link/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ async def authenticate(self, session: AsyncSession, token: str) -> User:
user.email_verified = True
await user.update(session)

await magic_link.delete(session)
# await magic_link.delete(session)

return user

Expand Down

0 comments on commit 7d20642

Please sign in to comment.