fix(auth): send login token in POST body

This commit is contained in:
Orvanix
2026-03-12 17:15:07 +00:00
parent b20673e7ea
commit 81712ad3ce
4 changed files with 20 additions and 15 deletions
+2 -1
View File
@@ -253,7 +253,8 @@ oidc.admin_claim_value = 'readmeabook-admin'
- Login token stored as SHA-256 hash in `User.loginTokenHash`
- Admin generates/revokes via user permissions modal
- User login with token `/auth/token/login?token=rmab_...`
- User navigates to `/auth/token/login?token=rmab_...` → page POSTs token to API in request body
- API: `POST /api/auth/token/login` with `{ token }` in JSON body
- Invalid token redirects to `/login`
## Security
+2 -2
View File
@@ -11,9 +11,9 @@ import crypto from 'crypto';
const logger = RMABLogger.create('API.Auth.TokenLogin');
export async function GET(request: NextRequest) {
export async function POST(request: NextRequest) {
try {
const token = request.nextUrl.searchParams.get('token');
const { token } = await request.json();
if (!token) {
return NextResponse.json({ error: 'Missing token parameter' }, { status: 400 });
+6 -2
View File
@@ -22,7 +22,11 @@ function TokenLoginContent() {
return;
}
fetch(`/api/auth/token/login?token=${encodeURIComponent(token)}`)
fetch('/api/auth/token/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token }),
})
.then((res) => res.json())
.then((data) => {
if (data.error) {
@@ -35,7 +39,7 @@ function TokenLoginContent() {
localStorage.setItem('user', JSON.stringify(data.user));
setAuthData(data.user, data.accessToken);
window.location.href = '/';
window.location.href = '/';
})
.catch(() => {
router.replace('/login');
+10 -10
View File
@@ -19,7 +19,7 @@ vi.mock('@/lib/utils/jwt', () => ({
generateRefreshToken: generateRefreshTokenMock,
}));
describe('GET /api/auth/token/login', () => {
describe('POST /api/auth/token/login', () => {
beforeEach(() => {
vi.clearAllMocks();
generateAccessTokenMock.mockReturnValue('access-token');
@@ -37,9 +37,9 @@ describe('GET /api/auth/token/login', () => {
});
prismaMock.user.update.mockResolvedValueOnce({});
const { GET } = await import('@/app/api/auth/token/login/route');
const request = { nextUrl: { searchParams: new URLSearchParams('token=rmab_valid_token') } };
const response = await GET(request as any);
const { POST } = await import('@/app/api/auth/token/login/route');
const request = { json: vi.fn().mockResolvedValue({ token: 'rmab_valid_token' }) };
const response = await POST(request as any);
const payload = await response.json();
expect(response.status).toBe(200);
@@ -50,9 +50,9 @@ describe('GET /api/auth/token/login', () => {
});
it('returns 400 when token parameter is missing', async () => {
const { GET } = await import('@/app/api/auth/token/login/route');
const request = { nextUrl: { searchParams: new URLSearchParams() } };
const response = await GET(request as any);
const { POST } = await import('@/app/api/auth/token/login/route');
const request = { json: vi.fn().mockResolvedValue({}) };
const response = await POST(request as any);
const payload = await response.json();
expect(response.status).toBe(400);
@@ -62,9 +62,9 @@ describe('GET /api/auth/token/login', () => {
it('returns 401 when token is invalid or user not found', async () => {
prismaMock.user.findFirst.mockResolvedValueOnce(null);
const { GET } = await import('@/app/api/auth/token/login/route');
const request = { nextUrl: { searchParams: new URLSearchParams('token=rmab_invalid') } };
const response = await GET(request as any);
const { POST } = await import('@/app/api/auth/token/login/route');
const request = { json: vi.fn().mockResolvedValue({ token: 'rmab_invalid' }) };
const response = await POST(request as any);
const payload = await response.json();
expect(response.status).toBe(401);