diff --git a/.kontinuous/env/preprod/values.yaml b/.kontinuous/env/preprod/values.yaml
index 188694183..29266b778 100644
--- a/.kontinuous/env/preprod/values.yaml
+++ b/.kontinuous/env/preprod/values.yaml
@@ -17,7 +17,7 @@ www:
host: cdtn-admin-preprod.dev.fabrique.social.gouv.fr
env:
- name: "FRONTEND_HOST"
- value: https://cdtn-admin-preprod.dev.fabrique.social.gouv.fr
+ value: cdtn-admin-preprod.dev.fabrique.social.gouv.fr
jobs:
runs:
diff --git a/.kontinuous/env/prod/values.yaml b/.kontinuous/env/prod/values.yaml
index f0f78e389..fb6ea866d 100644
--- a/.kontinuous/env/prod/values.yaml
+++ b/.kontinuous/env/prod/values.yaml
@@ -5,7 +5,7 @@ www:
nginx.ingress.kubernetes.io/whitelist-source-range: 185.24.184.196,185.24.185.196,185.24.186.196,185.24.187.196,185.24.187.254,164.131.160.1,164.131.160.2,164.131.160.3,164.131.160.4,164.131.160.5,164.131.160.6,164.131.160.17,164.131.160.18,164.131.160.19,164.131.160.20,164.131.160.21,164.131.160.22,164.131.160.33,164.131.160.34,164.131.160.35,164.131.160.36,164.131.160.37,164.131.160.38,164.131.160.49,164.131.160.50,164.131.160.51,164.131.160.52,164.131.160.53,164.131.160.54
env:
- name: "FRONTEND_HOST"
- value: https://cdtn-admin.fabrique.social.gouv.fr
+ value: cdtn-admin.fabrique.social.gouv.fr
resources:
limits:
cpu: "200m"
@@ -34,18 +34,18 @@ contributions:
export:
resources:
limits:
- cpu: '1500m'
- memory: '4096Mi'
+ cpu: "1500m"
+ memory: "4096Mi"
requests:
- cpu: '1000m'
- memory: '896Mi'
+ cpu: "1000m"
+ memory: "896Mi"
hasura:
replicas: 2
resources:
limits:
- cpu: '2000m'
- memory: '4Gi'
+ cpu: "2000m"
+ memory: "4Gi"
requests:
- cpu: '1000m'
- memory: '1Gi'
+ cpu: "1000m"
+ memory: "1Gi"
diff --git a/.node-version b/.node-version
index 2edeafb09..7639d8576 100644
--- a/.node-version
+++ b/.node-version
@@ -1 +1 @@
-20
\ No newline at end of file
+20.3.1
\ No newline at end of file
diff --git a/targets/frontend/next.config.js b/targets/frontend/next.config.js
index d639a2102..4a1386d32 100644
--- a/targets/frontend/next.config.js
+++ b/targets/frontend/next.config.js
@@ -1,9 +1,3 @@
-// Use the hidden-source-map option when you don't want the source maps to be
-// publicly available on the servers, only to the error reporting
-const withSourceMaps = require("@zeit/next-source-maps")();
-
-const basePath = "";
-
const securityHeaders = [
{
key: "X-Frame-Options",
@@ -14,7 +8,6 @@ const securityHeaders = [
];
module.exports = {
- basePath,
async headers() {
return [
{
@@ -25,6 +18,9 @@ module.exports = {
];
},
poweredByHeader: false,
+ httpAgentOptions: {
+ keepAlive: false,
+ },
webpack: (config, { isServer, dev }) => {
config.module.rules.push({
exclude: /node_modules/,
diff --git a/targets/frontend/package.json b/targets/frontend/package.json
index 9de3ac775..b5e8958c6 100644
--- a/targets/frontend/package.json
+++ b/targets/frontend/package.json
@@ -36,7 +36,6 @@
"@tiptap/react": "^2.1.10",
"@tiptap/starter-kit": "^2.0.3",
"@urql/exchange-auth": "^0.1.6",
- "@zeit/next-source-maps": "0.0.4-canary.1",
"ace-builds": "^1.4.12",
"argon2": "^0.30.3",
"cookie": "^0.4.1",
@@ -46,12 +45,12 @@
"diff": "^5.0.0",
"formidable": "^2.0.0",
"graphql": "^16.0.0",
- "http-proxy-middleware": "3.0.0-beta.1",
+ "http-proxy-middleware": "2.0.1",
"isomorphic-unfetch": "^3.1.0",
"jsonwebtoken": "^8.5.1",
"memoizee": "^0.4.15",
"micromark": "^2.11.4",
- "next": "13.2.4",
+ "next": "13.5.6",
"next-urql": "^3.2.1",
"nodemailer": "^6.6.5",
"p-limit": "^4.0.0",
@@ -85,6 +84,7 @@
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^14.0.0",
"@testing-library/user-event": "^14.5.1",
+ "@types/cookie": "^0.5.2",
"@types/formidable": "^2.0.5",
"@types/jest": "^27.4.0",
"@types/jsonwebtoken": "^9.0.3",
diff --git a/targets/frontend/src/components/editor/CodeEditor.js b/targets/frontend/src/components/editor/CodeEditor.js
index 4d0b6da0e..8635fe8c7 100644
--- a/targets/frontend/src/components/editor/CodeEditor.js
+++ b/targets/frontend/src/components/editor/CodeEditor.js
@@ -7,20 +7,24 @@ import "ace-builds/src-noconflict/theme-github";
export default function CodeEditor({ onChange, value }) {
return (
-
+ <>
+ {window && (
+
+ )}
+ >
);
}
diff --git a/targets/frontend/src/config.ts b/targets/frontend/src/config.ts
index c29a5f4ef..89fdc061e 100644
--- a/targets/frontend/src/config.ts
+++ b/targets/frontend/src/config.ts
@@ -2,7 +2,3 @@ export const ACCOUNT_MAIL_SENDER = "contact@fabrique.social.gouv.fr";
export const JWT_TOKEN_EXPIRES = 15; // 15 min
export const REFRESH_TOKEN_EXPIRES = 43200; // 30 days in minutes
export const ACTIVATION_TOKEN_EXPIRES = 10080; // 7 days in minutes
-export const HASURA_GRAPHQL_JWT_SECRET =
- process.env.HASURA_GRAPHQL_JWT_SECRET ??
- '{"type":"HS256","key":"a_pretty_long_secret_key_that_should_be_at_least_32_char"}';
-export const BASE_URL = process.env.FRONTEND_HOST || `http://localhost:3000`;
diff --git a/targets/frontend/src/hoc/CustomUrqlClient.js b/targets/frontend/src/hoc/CustomUrqlClient.js
index c79a07615..dfd3096f2 100644
--- a/targets/frontend/src/hoc/CustomUrqlClient.js
+++ b/targets/frontend/src/hoc/CustomUrqlClient.js
@@ -1,5 +1,4 @@
import { withUrqlClient } from "next-urql";
-import { BASE_URL } from "../config";
import {
customAuthExchange,
customErrorExchange,
@@ -9,13 +8,11 @@ import { cacheExchange, dedupExchange, fetchExchange } from "urql";
export const withCustomUrqlClient = (Component) =>
withUrqlClient(
(ssrExchange, ctx) => {
- const url = ctx?.req ? `${BASE_URL}/api/graphql` : `/api/graphql`;
- console.log(
- "[ withUrqlClient ]",
- ctx ? (ctx?.req ? "server" : "client") : "no ctx",
- ctx?.pathname,
- url
- );
+ const baseUrl = process.env.FRONTEND_HOST
+ ? `https://www.${process.env.FRONTEND_HOST}`
+ : `http://localhost:3000`;
+ const isServer = ctx && ctx.req;
+ const url = isServer ? `${baseUrl}/api/graphql` : "/api/graphql";
return {
exchanges: [
process.env.NODE_ENV !== "production"
diff --git a/targets/frontend/src/hoc/UserProvider.js b/targets/frontend/src/hoc/UserProvider.js
index 73600585a..8fcd50325 100644
--- a/targets/frontend/src/hoc/UserProvider.js
+++ b/targets/frontend/src/hoc/UserProvider.js
@@ -20,7 +20,6 @@ export function withUserProvider(WrappedComponent) {
static async getInitialProps(ctx) {
const token = await auth(ctx);
- console.log("[ withUserProvider ] ctx", ctx ? true : false);
const componentProps =
WrappedComponent.getInitialProps &&
(await WrappedComponent.getInitialProps(ctx));
diff --git a/targets/frontend/src/lib/auth/cookie.ts b/targets/frontend/src/lib/auth/cookie.ts
new file mode 100644
index 000000000..daf5c681c
--- /dev/null
+++ b/targets/frontend/src/lib/auth/cookie.ts
@@ -0,0 +1,56 @@
+import cookie from "cookie";
+import { REFRESH_TOKEN_EXPIRES } from "src/config";
+
+export function setJwtCookie(
+ res: any,
+ refresh_token?: string,
+ jwt_token?: string
+) {
+ const cookies = [];
+ try {
+ if (refresh_token) {
+ cookies.push(
+ cookie.serialize("refresh_token", refresh_token, {
+ httpOnly: true,
+ maxAge: REFRESH_TOKEN_EXPIRES * 60, // maxAge in second
+ path: "/",
+ sameSite: "strict",
+ secure: process.env.NODE_ENV === "production",
+ })
+ );
+ }
+ if (jwt_token) {
+ cookies.push(
+ cookie.serialize("jwt", jwt_token, {
+ httpOnly: true,
+ path: "/",
+ sameSite: "strict",
+ secure: process.env.NODE_ENV === "production",
+ })
+ );
+ }
+ if (cookies.length > 0) res.setHeader("Set-Cookie", cookies);
+ } catch (err) {
+ console.error("[setJwtCookie]", err);
+ }
+}
+
+export function removeJwtCookie(res: any) {
+ const cookies = [
+ cookie.serialize("refresh_token", "", {
+ httpOnly: true,
+ maxAge: -1,
+ path: "/",
+ sameSite: "strict",
+ secure: process.env.NODE_ENV === "production",
+ }),
+ cookie.serialize("jwt", "", {
+ httpOnly: true,
+ maxAge: -1,
+ path: "/",
+ sameSite: "strict",
+ secure: process.env.NODE_ENV === "production",
+ }),
+ ];
+ res.setHeader("Set-Cookie", cookies);
+}
diff --git a/targets/frontend/src/lib/auth/exchanges.js b/targets/frontend/src/lib/auth/exchanges.js
index 0b173e88d..08c414cbe 100644
--- a/targets/frontend/src/lib/auth/exchanges.js
+++ b/targets/frontend/src/lib/auth/exchanges.js
@@ -1,6 +1,6 @@
import { errorExchange, makeOperation } from "@urql/core";
import { authExchange } from "@urql/exchange-auth";
-import { auth, getToken, isTokenExpired, setToken } from "src/lib/auth/token";
+import { auth } from "src/lib/auth/token";
import { request } from "../request";
@@ -35,24 +35,6 @@ export function customAuthExchange(ctx) {
},
getAuth: async ({ authState }) => {
- // for initial launch, fetch the auth state from storage (local storage, async storage etc)
- if (!authState) {
- const token = getToken() || (await auth(ctx));
- if (token) {
- return { token: token.jwt_token };
- }
- return null;
- }
-
- /**
- * the following code gets executed when an auth error has occurred
- * we should refresh the token if possible and return a new auth state
- * If refresh fails, we should log out
- **/
-
- // if your refresh logic is in graphQL, you must use this mutate function to call it
- // if your refresh logic is a separate RESTful endpoint, use fetch or similar
- setToken(null);
const result = await auth(ctx);
if (result?.jwt_token) {
// return the new tokens
@@ -64,7 +46,7 @@ export function customAuthExchange(ctx) {
willAuthError: ({ authState }) => {
// e.g. check for expiration, existence of auth etc
- if (!authState || isTokenExpired()) return true;
+ if (!authState) return true;
return false;
},
});
diff --git a/targets/frontend/src/lib/auth/jwt.js b/targets/frontend/src/lib/auth/jwt.js
index 949b5c410..5943a0b7d 100644
--- a/targets/frontend/src/lib/auth/jwt.js
+++ b/targets/frontend/src/lib/auth/jwt.js
@@ -1,12 +1,13 @@
import jwt, { verify } from "jsonwebtoken";
-import { HASURA_GRAPHQL_JWT_SECRET } from "../../config";
-
import { JWT_TOKEN_EXPIRES } from "../../config";
let jwtSecret;
try {
- jwtSecret = JSON.parse(HASURA_GRAPHQL_JWT_SECRET);
+ jwtSecret = JSON.parse(
+ process.env.HASURA_GRAPHQL_JWT_SECRET ??
+ '{"type":"HS256","key":"a_pretty_long_secret_key_that_should_be_at_least_32_char"}'
+ );
} catch (error) {
console.error("[JWT], HASURA_GRAPHQL_JWT_SECRET is not a valid json");
}
diff --git a/targets/frontend/src/lib/auth/setJwtCookie.js b/targets/frontend/src/lib/auth/setJwtCookie.js
deleted file mode 100644
index 4a4a4ccdf..000000000
--- a/targets/frontend/src/lib/auth/setJwtCookie.js
+++ /dev/null
@@ -1,25 +0,0 @@
-import cookie from "cookie";
-import { REFRESH_TOKEN_EXPIRES } from "../../config";
-
-export function setJwtCookie(res, refresh_token, jwt_token) {
- const cookies = [
- cookie.serialize("refresh_token", refresh_token, {
- httpOnly: true,
- maxAge: parseInt(REFRESH_TOKEN_EXPIRES, 10) * 60, // maxAge in second
- path: "/",
- sameSite: "Strict",
- secure: process.env.NODE_ENV === "production",
- }),
- ];
- if (jwt_token) {
- cookies.push(
- cookie.serialize("jwt", jwt_token, {
- httpOnly: true,
- path: "/",
- sameSite: "Strict",
- secure: process.env.NODE_ENV === "production",
- })
- );
- }
- res.setHeader("Set-Cookie", cookies);
-}
diff --git a/targets/frontend/src/lib/auth/token.js b/targets/frontend/src/lib/auth/token.js
index d33a36756..b054af83b 100644
--- a/targets/frontend/src/lib/auth/token.js
+++ b/targets/frontend/src/lib/auth/token.js
@@ -1,30 +1,13 @@
import Router from "next/router";
-import { request } from "../request";
-import { setJwtCookie } from "./setJwtCookie";
-import { BASE_URL } from "../../config";
-
-let inMemoryToken;
-
-export function getToken() {
- return inMemoryToken || null;
-}
-
-export function setToken(token) {
- inMemoryToken = token;
-}
-
-export function isTokenExpired() {
- const expired =
- !inMemoryToken || Date.now() > new Date(inMemoryToken.jwt_token_expiry);
- return expired;
-}
+import { setJwtCookie, removeJwtCookie } from "./cookie";
+import cookie from "cookie";
export async function auth(ctx) {
- console.log("[ auth ] ctx ?", ctx ? true : false);
- if (ctx?.token) {
- return ctx.token;
- }
+ console.log(
+ "[auth] - refresh token => is server ?",
+ ctx && ctx.req ? true : false
+ );
const cookieHeader = ctx?.req ? { Cookie: ctx.req.headers.cookie } : {};
if (
@@ -32,41 +15,65 @@ export async function auth(ctx) {
!ctx.res.writableEnded &&
!/refresh_token/.test(cookieHeader.Cookie)
) {
- console.log("[ auth ] no cookie found -> redirect to login");
+ console.log("[auth] no cookie found -> redirect to login");
ctx.res.writeHead(302, { Location: "/login" });
ctx.res.end();
return null;
}
try {
- console.log("[auth] refresh token");
- const tokenData = await request(
- ctx?.req ? `${BASE_URL}/api/refresh_token` : "/api/refresh_token",
- {
- body: {},
- credentials: "include",
- headers: {
- "Cache-Control": "no-cache",
- ...cookieHeader,
- },
- mode: "same-origin",
+ const baseUrl = process.env.FRONTEND_HOST
+ ? `https://www.${process.env.FRONTEND_HOST}`
+ : `http://localhost:3000`;
+ const isServer = ctx && ctx.req;
+ const url = isServer
+ ? `${baseUrl}/api/refresh_token`
+ : "/api/refresh_token";
+
+ let body = {};
+
+ if (cookieHeader && cookieHeader.Cookie) {
+ let cookies = cookie.parse(cookieHeader.Cookie);
+ if (cookies && cookies.refresh_token) {
+ body = {
+ ...cookies,
+ };
}
- );
+ }
+ if (body.length === 0) {
+ console.log("no token found");
+ return;
+ }
+ const tokenData = await fetch(url, {
+ headers: {
+ "Content-Type": "application/json",
+ "Cache-Control": "no-cache",
+ ...cookieHeader,
+ },
+ method: "POST",
+ cache: "no-cache",
+ mode: "same-origin",
+ credentials: "include",
+ body: JSON.stringify({
+ refresh_token: body.refresh_token,
+ }),
+ }).then((res) => res.json());
+
+ if (!tokenData.refresh_token) {
+ throw new Error("no refresh_token found");
+ }
+
// for ServerSide call, we need to set the Cookie header
// to update the refresh_token value
- if (ctx?.res) {
+ if (isServer) {
setJwtCookie(ctx.res, tokenData.refresh_token);
- // we also store token in context (this is probably a bad idea b)
- // to reuse it and avoid refresh token twice
- ctx.token = tokenData;
}
- inMemoryToken = { ...tokenData };
- console.log("[auth] token", inMemoryToken ? "true" : "false");
- return inMemoryToken;
+ return tokenData;
} catch (error) {
console.error("[ auth ] refreshToken error ", { error });
// we are on server side and its response is not ended yet
if (ctx?.res && !ctx.res.writableEnded) {
+ removeJwtCookie(ctx.res);
ctx.res.writeHead(302, { Location: "/login" });
ctx.res.end();
} else if (ctx && !ctx.req) {
diff --git a/targets/frontend/src/lib/duration.js b/targets/frontend/src/lib/duration.js
index 0eb8f1380..6ca5488e7 100644
--- a/targets/frontend/src/lib/duration.js
+++ b/targets/frontend/src/lib/duration.js
@@ -10,7 +10,12 @@ export function toSecond(minutes = 0) {
}
export function getExpiryDate(minutes = 0) {
- return new Date(Date.now() + toMs(minutes));
+ try {
+ return new Date(Date.now() + toMs(minutes));
+ } catch (error) {
+ console.error(error);
+ return null;
+ }
}
export const timeSince = (date) =>
diff --git a/targets/frontend/src/lib/emails/activateAccount.ts b/targets/frontend/src/lib/emails/activateAccount.ts
index 5dc7e370e..71c6d18ef 100644
--- a/targets/frontend/src/lib/emails/activateAccount.ts
+++ b/targets/frontend/src/lib/emails/activateAccount.ts
@@ -1,9 +1,12 @@
-import { BASE_URL } from "src/config";
import sendmail from "./sendmail";
export function sendActivateAccountEmail(email: string, secret_token: string) {
const subject = "Activation de votre compte";
- const activateUrl = `${BASE_URL}/change_password?token=${secret_token}&activate=1`; // todo: dynamic hostname
+ const activateUrl = `${
+ process.env.FRONTEND_HOST
+ ? `https://www.${process.env.FRONTEND_HOST}`
+ : `http://localhost:3000`
+ }/change_password?token=${secret_token}&activate=1`; // todo: dynamic hostname
const text = `Bonjour,
Vous pouvez activer votre compte ${email} afin d'accéder à
l'outil d'administration du cdtn en suivant ce lien : ${activateUrl}
diff --git a/targets/frontend/src/lib/emails/lostPassword.js b/targets/frontend/src/lib/emails/lostPassword.js
index 35d6bae69..916c240eb 100644
--- a/targets/frontend/src/lib/emails/lostPassword.js
+++ b/targets/frontend/src/lib/emails/lostPassword.js
@@ -1,8 +1,11 @@
-import { BASE_URL } from "../../config";
import sendmail from "./sendmail";
export function sendLostPasswordEmail(email, secret_token) {
- const activateUrl = `${BASE_URL}/change_password?token=${secret_token}`; // todo: dynamic hostname
+ const activateUrl = `${
+ process.env.FRONTEND_HOST
+ ? `https://www.${process.env.FRONTEND_HOST}`
+ : `http://localhost:3000`
+ }/change_password?token=${secret_token}`; // todo: dynamic hostname
const subject = "Réinitialisation de votre mot de passe";
const text = `
Bonjour,
diff --git a/targets/frontend/src/pages/api/graphql.js b/targets/frontend/src/pages/api/graphql.js
index dc9fd13b0..457d0b980 100644
--- a/targets/frontend/src/pages/api/graphql.js
+++ b/targets/frontend/src/pages/api/graphql.js
@@ -23,7 +23,6 @@ const proxy = createProxyMiddleware({
process.env.HASURA_GRAPHQL_ENDPOINT ?? "http://localhost:8080/v1/graphql",
ws: true,
xfwd: true, // proxy websockets
- headers: { Connection: "keep-alive" },
});
export default proxy;
diff --git a/targets/frontend/src/pages/api/login.js b/targets/frontend/src/pages/api/login.js
index 5a2bfed2b..a4f87c728 100644
--- a/targets/frontend/src/pages/api/login.js
+++ b/targets/frontend/src/pages/api/login.js
@@ -4,7 +4,7 @@ import { client } from "@shared/graphql-client";
import { verify } from "argon2";
import { createErrorFor } from "src/lib/apiError";
import { generateJwtToken } from "src/lib/auth/jwt";
-import { setJwtCookie } from "src/lib/auth/setJwtCookie";
+import { setJwtCookie } from "src/lib/auth/cookie";
import { getExpiryDate } from "src/lib/duration";
import { loginQuery, refreshTokenMutation } from "./login.gql";
@@ -69,10 +69,11 @@ export default async function login(req, res) {
}
const jwt_token = generateJwtToken(user);
+
const refreshTokenResult = await client
.mutation(refreshTokenMutation, {
refresh_token_data: {
- expires_at: getExpiryDate(parseInt(REFRESH_TOKEN_EXPIRES, 10)),
+ expires_at: getExpiryDate(REFRESH_TOKEN_EXPIRES),
user_id: user.id,
},
})
@@ -93,7 +94,7 @@ export default async function login(req, res) {
res.json({
jwt_token,
- jwt_token_expiry: getExpiryDate(parseInt(JWT_TOKEN_EXPIRES, 10) || 15),
+ jwt_token_expiry: getExpiryDate(JWT_TOKEN_EXPIRES),
refresh_token,
});
}
diff --git a/targets/frontend/src/pages/api/logout.js b/targets/frontend/src/pages/api/logout.js
index eac7d2737..ef530b6a7 100644
--- a/targets/frontend/src/pages/api/logout.js
+++ b/targets/frontend/src/pages/api/logout.js
@@ -1,9 +1,8 @@
import Boom from "@hapi/boom";
import { z } from "zod";
import { client } from "@shared/graphql-client";
-import cookie from "cookie";
import { createErrorFor } from "src/lib/apiError";
-import { setToken } from "src/lib/auth/token";
+import { removeJwtCookie } from "src/lib/auth/cookie";
export default async function logout(req, res) {
const apiError = createErrorFor(res);
@@ -26,8 +25,6 @@ export default async function logout(req, res) {
const { refresh_token } = value;
- // delete JWT (optional)
- setToken(null);
// delete refresh token passed in data
const result = await client
.mutation(mutation, {
@@ -39,29 +36,8 @@ export default async function logout(req, res) {
console.error("logout error", result.error);
}
- console.log("[ logout ]", { refresh_token });
- res.setHeader(
- "Set-Cookie",
- cookie.serialize("refresh_token", "deleted", {
- httpOnly: true,
- maxAge: 0,
- path: "/",
- sameSite: "lax",
- secure: process.env.NODE_ENV === "production",
- })
- );
- res.setHeader(
- "Set-Cookie",
- cookie.serialize("jwt", "deleted", {
- httpOnly: true,
- maxAge: 0,
- path: "/",
- sameSite: "lax",
- secure: process.env.NODE_ENV === "production",
- })
- );
+ removeJwtCookie(res);
- console.log("[logout]", refresh_token);
res.json({ message: "user logout !" });
}
diff --git a/targets/frontend/src/pages/api/refresh_token.js b/targets/frontend/src/pages/api/refresh_token.js
index 5c574cbe5..4d33a861d 100644
--- a/targets/frontend/src/pages/api/refresh_token.js
+++ b/targets/frontend/src/pages/api/refresh_token.js
@@ -3,11 +3,10 @@ import { z } from "zod";
import { client } from "@shared/graphql-client";
import { createErrorFor } from "src/lib/apiError";
import { generateJwtToken } from "src/lib/auth/jwt";
-import { setJwtCookie } from "src/lib/auth/setJwtCookie";
import { getExpiryDate } from "src/lib/duration";
import { v4 as uuidv4 } from "uuid";
import { REFRESH_TOKEN_EXPIRES, JWT_TOKEN_EXPIRES } from "../../config";
-
+import { setJwtCookie } from "src/lib/auth/cookie";
import {
deletePreviousRefreshTokenMutation,
getRefreshTokenQuery,
@@ -15,78 +14,84 @@ import {
export default async function refreshToken(req, res) {
const apiError = createErrorFor(res);
- const schema = z.object({
- refresh_token: z.string().uuid(),
- });
-
- let { error, data: value } = schema.safeParse(req.query);
-
- if (error) {
- const temp = schema.safeParse(req.body);
- error = temp.error;
- value = temp.data;
- }
-
- if (error) {
- const temp = schema.safeParse(req.cookies);
- error = temp.error;
- value = temp.data;
- }
-
- const { refresh_token } = value;
-
- if (error) {
- return apiError(Boom.badRequest(error.details[0].message));
+ try {
+ console.log("[api/refresh_token.js] refreshToken");
+ const schema = z.object({
+ refresh_token: z.string().uuid(),
+ });
+
+ let value;
+
+ let { error, data } = schema.safeParse(req.query);
+
+ value = data;
+
+ if (error) {
+ const temp = schema.safeParse(req.body);
+ error = temp.error;
+ value = temp.data;
+ }
+
+ if (error) {
+ const temp = schema.safeParse(req.cookies);
+ error = temp.error;
+ value = temp.data;
+ }
+
+ if (!value.refresh_token) {
+ return apiError(Boom.unauthorized("Invalid 'refresh_token'"));
+ }
+
+ const { refresh_token } = value;
+
+ let result = await client
+ .query(getRefreshTokenQuery, {
+ current_timestampz: new Date(),
+ refresh_token,
+ })
+ .toPromise();
+
+ if (result.error) {
+ console.error(result.error);
+ return apiError(Boom.unauthorized("Invalid 'refresh_token'"));
+ }
+
+ if (result.data.refresh_tokens.length === 0) {
+ console.error("Incorrect user id or refresh token", refresh_token);
+ return apiError(Boom.unauthorized("Invalid 'refresh_token'"));
+ }
+
+ const { user } = result.data[`refresh_tokens`][0];
+
+ const new_refresh_token = uuidv4();
+
+ result = await client
+ .mutation(deletePreviousRefreshTokenMutation, {
+ new_refresh_token_data: {
+ expires_at: getExpiryDate(REFRESH_TOKEN_EXPIRES),
+ refresh_token: new_refresh_token,
+ user_id: user.id,
+ },
+ old_refresh_token: refresh_token,
+ })
+ .toPromise();
+
+ if (result.error) {
+ console.error(result.error);
+ return apiError(Boom.unauthorized("Invalid 'refresh_token'"));
+ }
+
+ const jwt_token = generateJwtToken(user);
+
+ setJwtCookie(res, new_refresh_token, jwt_token);
+
+ res.json({
+ jwt_token,
+ jwt_token_expiry: getExpiryDate(JWT_TOKEN_EXPIRES),
+ refresh_token: new_refresh_token,
+ });
+ } catch (e) {
+ console.error(e);
+ return apiError(Boom.badImplementation(e.message));
}
- let result = await client
- .query(getRefreshTokenQuery, {
- current_timestampz: new Date(),
- refresh_token,
- })
- .toPromise();
-
- if (result.error) {
- console.error(result.error);
- return apiError(Boom.unauthorized("Invalid 'refresh_token'"));
- }
-
- if (result.data.refresh_tokens.length === 0) {
- console.error("Incorrect user id or refresh token", refresh_token);
- return apiError(Boom.unauthorized("Invalid 'refresh_token'"));
- }
-
- const { user } = result.data[`refresh_tokens`][0];
-
- const new_refresh_token = uuidv4();
-
- console.log("[ /api/refresh_token ]", "replace", {
- new_refresh_token,
- refresh_token,
- });
-
- result = await client
- .mutation(deletePreviousRefreshTokenMutation, {
- new_refresh_token_data: {
- expires_at: getExpiryDate(parseInt(REFRESH_TOKEN_EXPIRES, 10)),
- refresh_token: new_refresh_token,
- user_id: user.id,
- },
- old_refresh_token: refresh_token,
- })
- .toPromise();
-
- if (result.error) {
- console.error(result.error);
- return apiError(Boom.unauthorized("Invalid 'refresh_token'"));
- }
-
- const jwt_token = generateJwtToken(user);
-
- setJwtCookie(res, new_refresh_token, jwt_token);
-
- res.json({
- jwt_token,
- jwt_token_expiry: getExpiryDate(parseInt(JWT_TOKEN_EXPIRES, 10) || 15),
- refresh_token: new_refresh_token,
- });
}
diff --git a/targets/frontend/src/pages/api/reset_password.js b/targets/frontend/src/pages/api/reset_password.js
index 89d6676a2..5e0152cc5 100644
--- a/targets/frontend/src/pages/api/reset_password.js
+++ b/targets/frontend/src/pages/api/reset_password.js
@@ -29,7 +29,7 @@ export default async function reset_password(req, res) {
const result = await client
.mutation(udpateSecretTokenMutation, {
email,
- expires: getExpiryDate(parseInt(ACTIVATION_TOKEN_EXPIRES, 10)),
+ expires: getExpiryDate(ACTIVATION_TOKEN_EXPIRES),
secret_token,
})
.toPromise();
diff --git a/targets/frontend/src/pages/api/sitemap.ts b/targets/frontend/src/pages/api/sitemap.ts
index 9b75eb412..3ae3c4571 100644
--- a/targets/frontend/src/pages/api/sitemap.ts
+++ b/targets/frontend/src/pages/api/sitemap.ts
@@ -66,7 +66,7 @@ export default async function Sitemap(
req: NextApiRequest,
res: NextApiResponse
) {
- console.log("[ /api/sitemap ]", " request: ", req.query);
+ console.log("[/api/sitemap]", " request: ", req.query);
const startProcessAt = process.hrtime();
const baseUrl =
(req.query.baseurl as string) || "https://code.travail.gouv.fr";
@@ -92,7 +92,7 @@ export default async function Sitemap(
`);
res.end();
const endProcess = process.hrtime(startProcessAt);
- console.log("[ /api/sitemap ]", " end in ", endProcess);
+ console.log("[/api/sitemap]", " end in ", endProcess);
}
/**
diff --git a/targets/frontend/src/pages/api/storage/[path].js b/targets/frontend/src/pages/api/storage/[path].js
index 20fef9633..b6a471ac0 100644
--- a/targets/frontend/src/pages/api/storage/[path].js
+++ b/targets/frontend/src/pages/api/storage/[path].js
@@ -2,14 +2,16 @@ import Boom from "@hapi/boom";
import { verify } from "jsonwebtoken";
import { createErrorFor } from "src/lib/apiError";
import { deleteBlob } from "src/lib/azure";
-import { HASURA_GRAPHQL_JWT_SECRET } from "../../../config";
const container = process.env.STORAGE_CONTAINER ?? "cdtn-dev";
-const jwtSecret = JSON.parse(HASURA_GRAPHQL_JWT_SECRET);
+const jwtSecret = JSON.parse(
+ process.env.HASURA_GRAPHQL_JWT_SECRET ??
+ '{"type":"HS256","key":"a_pretty_long_secret_key_that_should_be_at_least_32_char"}'
+);
export default async function deleteFiles(req, res) {
const apiError = createErrorFor(res);
- const { token } = req.headers;
+ const { jwt: token } = req.cookies;
if (!token || !verify(token, jwtSecret.key, { algorithms: jwtSecret.type })) {
return apiError(Boom.badRequest("wrong token"));
diff --git a/targets/frontend/src/pages/api/storage/index.ts b/targets/frontend/src/pages/api/storage/index.ts
index b100e7f0b..6c7386d3d 100644
--- a/targets/frontend/src/pages/api/storage/index.ts
+++ b/targets/frontend/src/pages/api/storage/index.ts
@@ -5,15 +5,17 @@ import { createErrorFor } from "src/lib/apiError";
import { getContainerBlobs, uploadBlob } from "src/lib/azure";
import { isUploadFileSafe } from "src/lib/secu";
import * as stream from "stream";
-import { HASURA_GRAPHQL_JWT_SECRET } from "../../../config";
import { NextApiRequest, NextApiResponse } from "next";
const container = process.env.STORAGE_CONTAINER ?? "cdtn-dev";
-const jwtSecret = JSON.parse(HASURA_GRAPHQL_JWT_SECRET);
+const jwtSecret = JSON.parse(
+ process.env.HASURA_GRAPHQL_JWT_SECRET ??
+ '{"type":"HS256","key":"a_pretty_long_secret_key_that_should_be_at_least_32_char"}'
+);
async function endPoint(req: NextApiRequest, res: NextApiResponse) {
const apiError = createErrorFor(res);
- const { token }: any = req.headers;
+ const { jwt: token } = req.cookies;
if (!token || !verify(token, jwtSecret.key, { algorithms: jwtSecret.type })) {
return apiError(Boom.badRequest("wrong token"));
diff --git a/targets/frontend/src/pages/contenus/[id].tsx b/targets/frontend/src/pages/contenus/[id].tsx
index 187d75017..4449d5905 100644
--- a/targets/frontend/src/pages/contenus/[id].tsx
+++ b/targets/frontend/src/pages/contenus/[id].tsx
@@ -1,7 +1,7 @@
import dynamic from "next/dynamic";
import Link from "next/link";
import { useRouter } from "next/router";
-import React, { useState } from "react";
+import React, { useEffect, useState } from "react";
import { useForm } from "react-hook-form";
import { Button } from "src/components/button";
import { Layout } from "src/components/layout/auth.layout";
@@ -23,6 +23,7 @@ const CodeWithCodemirror = dynamic(import("src/components/editor/CodeEditor"), {
export function DocumentPage() {
const router = useRouter();
+ const [dataDocument, setDataDocument] = useState(undefined);
const [result] = useQuery({
query: getDocumentQuery,
@@ -30,12 +31,25 @@ export function DocumentPage() {
id: router.query.id,
},
});
- const { fetching, error, data: dataDocument } = result;
+
+ useEffect(() => {
+ return () => {
+ setDataDocument(undefined);
+ setJsonData(undefined);
+ };
+ }, []);
+
+ useEffect(() => {
+ if (result && result.data && result.data.document) {
+ setDataDocument(result.data.document);
+ setJsonData(JSON.stringify(result.data.document, undefined, 2));
+ }
+ }, [JSON.stringify(result)]);
+
+ const { fetching, error } = result;
const [hasChanged, setHasChanged] = useState(false);
- const [jsonData, setJsonData] = useState(
- JSON.stringify(dataDocument?.document, undefined, 2)
- );
+ const [jsonData, setJsonData] = useState(undefined);
const [, executeUpdate] = useMutation(updateDocumentMutation);
const [, previewContent] = useMutation(previewContentAction);
const { handleSubmit } = useForm();
@@ -92,20 +106,19 @@ export function DocumentPage() {
);
}
return (
-
+