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

Modernize Login #882

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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
13 changes: 13 additions & 0 deletions apps/www/graphql/republik-api/mutations/AuthorizeSession.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
mutation authorizeSession(
$email: String!
$tokens: [SignInToken!]!
$consents: [String!]
$requiredFields: RequiredUserFields
) {
authorizeSession(
email: $email
tokens: $tokens
consents: $consents
requiredFields: $requiredFields
)
}
19 changes: 19 additions & 0 deletions apps/www/graphql/republik-api/mutations/SignIn.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
mutation signIn(
$email: String!
$context: String
$consents: [String!]
$tokenType: SignInTokenType
$accessToken: ID
) {
signIn(
email: $email
context: $context
consents: $consents
tokenType: $tokenType
accessToken: $accessToken
) {
phrase
tokenType
alternativeFirstFactors
}
}
3 changes: 3 additions & 0 deletions apps/www/graphql/republik-api/mutations/SignOut.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mutation signOut {
signOut
}
29 changes: 29 additions & 0 deletions apps/www/graphql/republik-api/mutations/UnauthorizedSession.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
query unauthorizedSession(
$email: String!
$token: String!
$tokenType: SignInTokenType!
) {
echo {
ipAddress
userAgent
country
city
}
unauthorizedSession(
email: $email
token: { type: $tokenType, payload: $token }
) {
newUser
enabledSecondFactors
requiredConsents
requiredFields
session {
ipAddress
userAgent
country
city
phrase
isCurrent
}
}
}
14 changes: 14 additions & 0 deletions apps/www/src/app/auth/confirm-dialog/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export default async function ConfirmDialog({ searchParams }) {
return (
<form action='/auth/confirm' method='POST'>
<pre>{JSON.stringify(searchParams, null, 2)}</pre>

{Object.entries(searchParams).map(([k, v]) => {
return <input key={k} name={k} type='text' value={v as string} />
})}

<button type='submit'>Bestätigen</button>
<button type='submit'>Ablehnen</button>
</form>
)
}
88 changes: 88 additions & 0 deletions apps/www/src/app/auth/confirm/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {
AuthorizeSessionDocument,
SignInTokenType,
UnauthorizedSessionDocument,
} from '#graphql/republik-api/__generated__/gql/graphql'
import { getClient } from '@app/lib/apollo/client'
import { NextResponse, type NextRequest } from 'next/server'

// TODO: add more types
type EmailOtpType = 'token-authorization'

export async function GET(request: NextRequest) {
const { searchParams } = request.nextUrl
// const token_hash = searchParams.get('token_hash')
const type = searchParams.get('type') as EmailOtpType | null
const email = Buffer.from(searchParams.get('email') ?? '', 'base64').toString(
'utf8',
)
const token = searchParams.get('token')

// Use context param for redirect whoop
const context = searchParams.get('context')

let redirectTo: URL
try {
// TODO: validate context as URL/pathname
redirectTo = new URL(context ?? '/', request.nextUrl)
} catch (e) {
redirectTo = new URL('/', request.nextUrl)
}

const gqlClient = getClient()

try {
const { data } = await gqlClient.query({
query: UnauthorizedSessionDocument,
variables: {
email,
token,
tokenType: SignInTokenType.EmailToken,
},
})

// If not the current session, show confirm dialog
if (!data.unauthorizedSession.session.isCurrent) {
// TODO: make a new dialog
redirectTo.pathname = '/mitteilung'
return NextResponse.redirect(redirectTo)
}

// If current session, just authorize and redirect to target
await gqlClient.mutate({
mutation: AuthorizeSessionDocument,
variables: {
email,
tokens: [{ type: SignInTokenType.EmailToken, payload: token }],
},
})

return NextResponse.redirect(redirectTo)
} catch (e) {
throw Error(e)
// return NextResponse.redirect(redirectTo)
}
}

export async function POST(request: NextRequest) {
const fd = await request.formData()

const email = Buffer.from(
(fd.get('email') as string) ?? '',
'base64',
).toString('utf8')

const token = fd.get('token') as string

const gqlClient = getClient()

await gqlClient.mutate({
mutation: AuthorizeSessionDocument,
variables: {
email,
tokens: [{ type: SignInTokenType.EmailToken, payload: token }],
},
})

return NextResponse.redirect(new URL('/auth/confirm-ok', request.nextUrl))
}
31 changes: 31 additions & 0 deletions apps/www/src/app/auth/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Container from '@app/components/container'
import { PageLayout } from '@app/components/layout'
import { css } from '@republik/theme/css'
import { Metadata } from 'next'

export const metadata: Metadata = {
title: {
default: 'Anmelden',
template: '%s – Anmelden – Republik',
},
}

export const revalidate = 60 // revalidate all event pages every minute

export default async function Layout(props: { children: React.ReactNode }) {
return (
<div>
<PageLayout>
<div
className={css({
color: 'text',
pb: '16-32',
pt: '4',
})}
>
<Container>{props.children}</Container>
</div>
</PageLayout>
</div>
)
}
Loading
Loading