Skip to content
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
6 changes: 4 additions & 2 deletions apps/frontend/app/(public)/auth/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { useLoginForm } from '@/features/public/auth/login/hooks';
import { appendCallbackUrl, normalizeCallbackUrl } from '@/lib/auth/callback-url';

export default function LoginPage() {
return (
Expand All @@ -19,7 +20,8 @@ export default function LoginPage() {
function LoginPageContent() {
const router = useRouter();
const searchParams = useSearchParams();
const callbackUrl = searchParams.get('callbackUrl') ?? '/dashboard';
const callbackUrl = normalizeCallbackUrl(searchParams.get('callbackUrl'));
const registerHref = appendCallbackUrl('/auth/register', callbackUrl);
const { form, error, validators } = useLoginForm(() => {
router.push(callbackUrl);
});
Expand Down Expand Up @@ -90,7 +92,7 @@ function LoginPageContent() {
</p>
<p className='text-sm text-center text-muted-foreground mt-4'>
アカウントをお持ちでない方は{' '}
<Link href='/auth/register' className='text-primary hover:underline'>
<Link href={registerHref} className='text-primary hover:underline'>
新規登録
</Link>
</p>
Expand Down
31 changes: 24 additions & 7 deletions apps/frontend/app/(public)/auth/register/page.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,36 @@
'use client';

import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useState } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { Suspense, useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { useRegisterForm } from '@/features/public/auth/register/hooks';
import { appendCallbackUrl, normalizeCallbackUrl } from '@/lib/auth/callback-url';

export default function RegisterPage() {
return (
<Suspense fallback={null}>
<RegisterPageContent />
</Suspense>
);
}

function RegisterPageContent() {
const router = useRouter();
const searchParams = useSearchParams();
const rawCallbackUrl = searchParams.get('callbackUrl');
const callbackUrl = normalizeCallbackUrl(rawCallbackUrl);
const hasCallbackUrl = rawCallbackUrl !== null;
const loginHref = hasCallbackUrl ? appendCallbackUrl('/auth/login', callbackUrl) : '/auth/login';
const [registeredEmail, setRegisteredEmail] = useState<string | null>(null);
const { form, error, validators } = useRegisterForm((email) => {
setRegisteredEmail(email);
});
const { form, error, validators } = useRegisterForm(
(email) => {
setRegisteredEmail(email);
},
hasCallbackUrl ? callbackUrl : undefined,
);

if (registeredEmail) {
return (
Expand All @@ -27,7 +44,7 @@ export default function RegisterPage() {
<p className='text-sm text-muted-foreground'>
メール内のリンクを開くとアカウント作成が完了します。
</p>
<Button type='button' className='w-full' onClick={() => router.push('/auth/login')}>
<Button type='button' className='w-full' onClick={() => router.push(loginHref)}>
ログインへ
</Button>
</CardContent>
Expand Down Expand Up @@ -146,7 +163,7 @@ export default function RegisterPage() {
</form>
<p className='text-sm text-center text-muted-foreground mt-4'>
すでにアカウントをお持ちの方は{' '}
<Link href='/auth/login' className='text-primary hover:underline'>
<Link href={loginHref} className='text-primary hover:underline'>
ログイン
</Link>
</p>
Expand Down
12 changes: 5 additions & 7 deletions apps/frontend/app/(public)/invite/[invitationId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ export default function InvitePage({ params }: { params: Promise<{ invitationId:
const router = useRouter();
const [status, setStatus] = useState<'idle' | 'loading' | 'error'>('idle');
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const inviteCallbackUrl = `/invite/${encodeURIComponent(invitationId)}`;
const loginHref = `/auth/login?callbackUrl=${encodeURIComponent(inviteCallbackUrl)}`;
const registerHref = `/auth/register?callbackUrl=${encodeURIComponent(inviteCallbackUrl)}`;

const handleAccept = async () => {
setStatus('loading');
Expand Down Expand Up @@ -55,13 +58,8 @@ export default function InvitePage({ params }: { params: Promise<{ invitationId:
<CardDescription>ログインまたはアカウントを作成してから承認できます</CardDescription>
</CardHeader>
<CardContent className='flex flex-col gap-3'>
<Button render={<Link href={`/auth/login?callbackUrl=/invite/${invitationId}`} />}>
ログイン
</Button>
<Button
variant='outline'
render={<Link href={`/auth/register?callbackUrl=/invite/${invitationId}`} />}
>
<Button render={<Link href={loginHref} />}>ログイン</Button>
<Button variant='outline' render={<Link href={registerHref} />}>
アカウントを作成
</Button>
</CardContent>
Expand Down
9 changes: 7 additions & 2 deletions apps/frontend/features/public/auth/register/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { z } from 'zod';
import { useInvalidateMe } from '@/contexts/AuthContext';
import { authClient } from '@/lib/auth/client';

export function useRegisterForm(onSuccess: (email: string) => void) {
export function useRegisterForm(
onSuccess: (email: string) => void,
verificationCallbackUrl?: string,
) {
Comment on lines 4 to +10
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Import appendCallbackUrl to correctly construct the email verification URL with the target callback destination.

Suggested change
import { useInvalidateMe } from '@/contexts/AuthContext';
import { authClient } from '@/lib/auth/client';
export function useRegisterForm(onSuccess: (email: string) => void) {
export function useRegisterForm(
onSuccess: (email: string) => void,
verificationCallbackUrl?: string,
) {
import { useInvalidateMe } from '@/contexts/AuthContext';
import { authClient } from '@/lib/auth/client';
import { appendCallbackUrl } from '@/lib/auth/callback-url';
export function useRegisterForm(
onSuccess: (email: string) => void,
verificationCallbackUrl?: string,
) {

const invalidateMe = useInvalidateMe();
const [error, setError] = useState<string | null>(null);

Expand All @@ -16,7 +19,9 @@ export function useRegisterForm(onSuccess: (email: string) => void) {
name: value.name,
email: value.email,
password: value.password,
callbackURL: `${window.location.origin}/auth/verify-email`,
callbackURL: verificationCallbackUrl
? new URL(verificationCallbackUrl, window.location.origin).toString()
: `${window.location.origin}/auth/verify-email`,
Comment on lines +22 to +24
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current implementation replaces the email verification URL entirely with the verificationCallbackUrl. This will break the registration flow because the user will be directed to a page (like /dashboard or /invite/...) that does not handle the email verification token. Instead, the callbackURL sent to the auth provider should point to the verification endpoint, which then handles the redirection to the final destination.

Suggested change
callbackURL: verificationCallbackUrl
? new URL(verificationCallbackUrl, window.location.origin).toString()
: `${window.location.origin}/auth/verify-email`,
callbackURL: verificationCallbackUrl
? appendCallbackUrl(`${window.location.origin}/auth/verify-email`, verificationCallbackUrl)
: `${window.location.origin}/auth/verify-email`,

});

if (result.error) {
Expand Down
15 changes: 15 additions & 0 deletions apps/frontend/lib/auth/callback-url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const DEFAULT_CALLBACK_URL = '/dashboard';

export function normalizeCallbackUrl(callbackUrl: string | null): string {
if (!callbackUrl) return DEFAULT_CALLBACK_URL;
if (!callbackUrl.startsWith('/') || callbackUrl.startsWith('//')) {
return DEFAULT_CALLBACK_URL;
}

return callbackUrl;
}

export function appendCallbackUrl(path: string, callbackUrl: string): string {
const params = new URLSearchParams({ callbackUrl });
return `${path}?${params.toString()}`;
}
Loading