mirror of
https://github.com/kikootwo/ReadMeABook.git
synced 2026-06-03 12:50:09 +00:00
fix(auth): send login token in POST body
This commit is contained in:
@@ -253,7 +253,8 @@ oidc.admin_claim_value = 'readmeabook-admin'
|
|||||||
|
|
||||||
- Login token stored as SHA-256 hash in `User.loginTokenHash`
|
- Login token stored as SHA-256 hash in `User.loginTokenHash`
|
||||||
- Admin generates/revokes via user permissions modal
|
- 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`
|
- Invalid token redirects to `/login`
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import crypto from 'crypto';
|
|||||||
|
|
||||||
const logger = RMABLogger.create('API.Auth.TokenLogin');
|
const logger = RMABLogger.create('API.Auth.TokenLogin');
|
||||||
|
|
||||||
export async function GET(request: NextRequest) {
|
export async function POST(request: NextRequest) {
|
||||||
try {
|
try {
|
||||||
const token = request.nextUrl.searchParams.get('token');
|
const { token } = await request.json();
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return NextResponse.json({ error: 'Missing token parameter' }, { status: 400 });
|
return NextResponse.json({ error: 'Missing token parameter' }, { status: 400 });
|
||||||
|
|||||||
@@ -22,7 +22,11 @@ function TokenLoginContent() {
|
|||||||
return;
|
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((res) => res.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.error) {
|
if (data.error) {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ vi.mock('@/lib/utils/jwt', () => ({
|
|||||||
generateRefreshToken: generateRefreshTokenMock,
|
generateRefreshToken: generateRefreshTokenMock,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('GET /api/auth/token/login', () => {
|
describe('POST /api/auth/token/login', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
generateAccessTokenMock.mockReturnValue('access-token');
|
generateAccessTokenMock.mockReturnValue('access-token');
|
||||||
@@ -37,9 +37,9 @@ describe('GET /api/auth/token/login', () => {
|
|||||||
});
|
});
|
||||||
prismaMock.user.update.mockResolvedValueOnce({});
|
prismaMock.user.update.mockResolvedValueOnce({});
|
||||||
|
|
||||||
const { GET } = await import('@/app/api/auth/token/login/route');
|
const { POST } = await import('@/app/api/auth/token/login/route');
|
||||||
const request = { nextUrl: { searchParams: new URLSearchParams('token=rmab_valid_token') } };
|
const request = { json: vi.fn().mockResolvedValue({ token: 'rmab_valid_token' }) };
|
||||||
const response = await GET(request as any);
|
const response = await POST(request as any);
|
||||||
const payload = await response.json();
|
const payload = await response.json();
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
expect(response.status).toBe(200);
|
||||||
@@ -50,9 +50,9 @@ describe('GET /api/auth/token/login', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('returns 400 when token parameter is missing', async () => {
|
it('returns 400 when token parameter is missing', async () => {
|
||||||
const { GET } = await import('@/app/api/auth/token/login/route');
|
const { POST } = await import('@/app/api/auth/token/login/route');
|
||||||
const request = { nextUrl: { searchParams: new URLSearchParams() } };
|
const request = { json: vi.fn().mockResolvedValue({}) };
|
||||||
const response = await GET(request as any);
|
const response = await POST(request as any);
|
||||||
const payload = await response.json();
|
const payload = await response.json();
|
||||||
|
|
||||||
expect(response.status).toBe(400);
|
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 () => {
|
it('returns 401 when token is invalid or user not found', async () => {
|
||||||
prismaMock.user.findFirst.mockResolvedValueOnce(null);
|
prismaMock.user.findFirst.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
const { GET } = await import('@/app/api/auth/token/login/route');
|
const { POST } = await import('@/app/api/auth/token/login/route');
|
||||||
const request = { nextUrl: { searchParams: new URLSearchParams('token=rmab_invalid') } };
|
const request = { json: vi.fn().mockResolvedValue({ token: 'rmab_invalid' }) };
|
||||||
const response = await GET(request as any);
|
const response = await POST(request as any);
|
||||||
const payload = await response.json();
|
const payload = await response.json();
|
||||||
|
|
||||||
expect(response.status).toBe(401);
|
expect(response.status).toBe(401);
|
||||||
|
|||||||
Reference in New Issue
Block a user