diff --git a/src/pages/admin/login.tsx b/src/pages/admin/login.tsx index 90eb3cc..b619689 100644 --- a/src/pages/admin/login.tsx +++ b/src/pages/admin/login.tsx @@ -1,8 +1,16 @@ -import React, { useState, useEffect } from "react"; +import React, { + useState, + useEffect, +} from "react"; import { NextPage } from "next"; import { useRouter } from "next/router"; import styled from "styled-components"; -import { generateToken, setTokenCookie, isAuthenticated } from "@utils/auth"; +import { + generateToken, + setAccessTokenCookie, + setRefreshTokenCookie, + isAuthenticated, +} from "@utils/auth"; import AdminPageWrapper from "@views/common/AdminPageWrapper"; const Main = styled.div` @@ -30,8 +38,11 @@ const AdminLoginPage: NextPage = () => { const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); try { - const token = await generateToken(username, password); - setTokenCookie(token); + const { access, refresh } = await generateToken(username, password); + + setAccessTokenCookie(access); + setRefreshTokenCookie(refresh); + router.push(next); } catch (err) { setError("Failed to log in!"); diff --git a/src/pages/admin/logout.tsx b/src/pages/admin/logout.tsx index 9c506ef..6b548f5 100644 --- a/src/pages/admin/logout.tsx +++ b/src/pages/admin/logout.tsx @@ -1,12 +1,12 @@ import { NextPage } from "next"; import { useRouter } from "next/router"; -import { deleteTokenCookie } from "@utils/auth"; +import { deleteTokenCookies } from "@utils/auth"; const AdminLogoutPage: NextPage = () => { const router = useRouter(); // client-side-only code if (typeof window !== "undefined") { - deleteTokenCookie(); + deleteTokenCookies(); router.push("/admin/login"); } return null; diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 4300fff..e425fe9 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -1,46 +1,80 @@ import axios from "axios"; import Cookies from "js-cookie"; -const tokenUrl = `${process.env.NEXT_PUBLIC_API_URL}/api-token-auth/`; -const checkUrl = `${process.env.NEXT_PUBLIC_API_URL}/api-token-verify/`; +const tokenUrl = `${process.env.NEXT_PUBLIC_API_URL}/token/`; +const checkUrl = `${process.env.NEXT_PUBLIC_API_URL}/token/verify/`; +const refreshUrl = `${process.env.NEXT_PUBLIC_API_URL}/token/refresh/`; -export async function generateToken(username: string, password: string): Promise { +export async function generateToken(username: string, password: string): Promise<{"access":string, "refresh":string}> { const resp = await axios.post(tokenUrl, { username, password, }); - return resp.data.token; + return { + "access": resp.data.access, + "refresh": resp.data.refresh, + }; } -export function setTokenCookie(token: string): void { - Cookies.set("jwt", token); - Cookies.set("jwt", token, { domain: ".sahkoinsinoorikilta.fi" }); +export function setAccessTokenCookie(access_token: string): void { + Cookies.set("jwt_access", access_token); + Cookies.set("jwt_access", access_token, { domain: ".sahkoinsinoorikilta.fi" }); } -export function getTokenCookie(): string { - return Cookies.get("jwt"); +export function setRefreshTokenCookie(refresh_token: string): void { + Cookies.set("jwt_refresh", refresh_token); + Cookies.set("jwt_refresh", refresh_token, { domain: ".sahkoinsinoorikilta.fi" }); } -export function deleteTokenCookie(): void { - Cookies.remove("jwt", { domain: ".sahkoinsinoorikilta.fi" }); - Cookies.remove("jwt"); +export function getAccessTokenCookie(): string { + return Cookies.get("jwt_access"); +} + +export function getRefreshTokenCookie(): string { + return Cookies.get("jwt_refresh"); +} + +export function deleteTokenCookies(): void { + Cookies.remove("jwt_access", { domain: ".sahkoinsinoorikilta.fi" }); + Cookies.remove("jwt_access"); + Cookies.remove("jwt_refresh", { domain: ".sahkoinsinoorikilta.fi" }); + Cookies.remove("jwt_refresh"); } export async function isAuthenticated(): Promise { try { - const token = getTokenCookie(); - await axios.post(checkUrl, { + const token = getAccessTokenCookie(); + + await axios.post(checkUrl, { token, }); + return true; } catch (err) { - // remove the cookie since it's invalid - deleteTokenCookie(); + return refreshToken(); + } +} + +export async function refreshToken(): Promise { + try { + const refresh = getRefreshTokenCookie(); + if (refresh) { + const resp = await axios.post(refreshUrl, { + refresh, + }); + + setAccessTokenCookie(resp.data.access); + + return true; + } + return false; + } catch (err) { + deleteTokenCookies(); return false; } } export function getAuthHeader(): string { - const jwt = getTokenCookie(); - return `JWT ${jwt}`; + const jwt = getAccessTokenCookie(); + return `Bearer ${jwt}`; } diff --git a/tests/testcafe/admin/login.test.ts b/tests/testcafe/admin/login.test.ts index 13e0797..5eabb67 100644 --- a/tests/testcafe/admin/login.test.ts +++ b/tests/testcafe/admin/login.test.ts @@ -39,7 +39,7 @@ test("User is redirected to login when JWT token is invalid", async (t) => { * Test if the user is redirected to login when JWT token is invalid. */ const setCookie = ClientFunction(() => { - document.cookie = "jwt=FOOBAR"; + document.cookie = "jwt_access=FOOBAR"; }); await setCookie(); await t.navigateTo("/admin/events"); diff --git a/tests/testcafe/utils.ts b/tests/testcafe/utils.ts index efee7e3..ddd760b 100644 --- a/tests/testcafe/utils.ts +++ b/tests/testcafe/utils.ts @@ -27,7 +27,7 @@ export const doLogin = async (t: TestController) => { }; export async function generateToken(): Promise { - const tokenUrl = `${API_URL}/api-token-auth/`; + const tokenUrl = `${API_URL}/token/`; try { const resp = await axios.post(tokenUrl, { @@ -43,11 +43,11 @@ export async function generateToken(): Promise { const eventURL = `${API_URL}/events/`; -export async function createEvent(data, jwt: string) { +export async function createEvent(data, jwt_access: string) { try { const resp = await axios.post(eventURL, data, { headers: { - Authorization: `JWT ${jwt}`, + Authorization: `Bearer ${jwt_access}`, }, }); return resp.data; @@ -57,11 +57,11 @@ export async function createEvent(data, jwt: string) { } } -export async function deleteEvent(id: string, jwt: string) { +export async function deleteEvent(id: string, jwt_access: string) { try { const resp = await axios.delete(`${eventURL}${id}/`, { headers: { - Authorization: `JWT ${jwt}`, + Authorization: `Bearer ${jwt_access}`, }, }); return resp.data; @@ -73,11 +73,11 @@ export async function deleteEvent(id: string, jwt: string) { const formURL = `${API_URL}/signupForm/`; -export async function createForm(data, jwt: string) { +export async function createForm(data, jwt_access: string) { try { const resp = await axios.post(formURL, data, { headers: { - Authorization: `JWT ${jwt}`, + Authorization: `Bearer ${jwt_access}`, }, }); return resp.data; @@ -87,11 +87,11 @@ export async function createForm(data, jwt: string) { } } -export async function deleteForm(id: string, jwt: string) { +export async function deleteForm(id: string, jwt_access: string) { try { const resp = await axios.delete(`${formURL}${id}/`, { headers: { - Authorization: `JWT ${jwt}`, + Authorization: `Bearer ${jwt_access}`, }, }); return resp.data; @@ -101,7 +101,7 @@ export async function deleteForm(id: string, jwt: string) { } } -export const generateTestForm = async (jwt: string) => ( +export const generateTestForm = async (jwt_access: string) => ( createForm({ title_fi: "Testi Ilmo", title_en: "Test Signup", @@ -132,10 +132,10 @@ export const generateTestForm = async (jwt: string) => ( }, }, }, - }, jwt) + }, jwt_access) ); -export const generateTestEvent = async (formIds = [], jwt: string) => ( +export const generateTestEvent = async (formIds = [], jwt_access: string) => ( createEvent({ tags: [1], visible: true, @@ -153,7 +153,7 @@ export const generateTestEvent = async (formIds = [], jwt: string) => ( signupForm: formIds, signup_id: formIds, tag_id: [1], - }, jwt) + }, jwt_access) ); export const sleep = async (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));