mirror of
https://github.com/Paris-est-Ludique/intranet.git
synced 2025-09-11 05:46:28 +02:00
Secure with jwt
This commit is contained in:
@@ -15,4 +15,5 @@ export default {
|
||||
},
|
||||
],
|
||||
},
|
||||
JWT_SECRET: "RblQqA6uF#msq2312bebf2FLFn4XzWQ6dttXSJwBX#?gL2JWf!",
|
||||
}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import path from "path"
|
||||
import express from "express"
|
||||
import express, { RequestHandler } from "express"
|
||||
import logger from "morgan"
|
||||
import compression from "compression"
|
||||
import helmet from "helmet"
|
||||
@@ -15,6 +15,7 @@ import devServer from "./devServer"
|
||||
import ssr from "./ssr"
|
||||
|
||||
import certbotRouter from "../routes/certbot"
|
||||
import { secure } from "./secure"
|
||||
import { jeuJavListGet } from "./gsheets/jeuJav"
|
||||
import { envieListGet, envieAdd } from "./gsheets/envies"
|
||||
import { membreGet, membreSet } from "./gsheets/membres"
|
||||
@@ -43,21 +44,23 @@ app.use(express.static(path.resolve(process.cwd(), "public")))
|
||||
// Enable dev-server in development
|
||||
if (__DEV__) devServer(app)
|
||||
|
||||
/**
|
||||
* APIs
|
||||
*/
|
||||
|
||||
// Google Sheets API
|
||||
app.use(express.json())
|
||||
app.get("/JeuJavListGet", jeuJavListGet)
|
||||
app.get("/EnvieListGet", envieListGet)
|
||||
app.get("/MembreGet", membreGet)
|
||||
app.post("/MembreSet", membreSet)
|
||||
app.post("/EnvieAdd", envieAdd)
|
||||
|
||||
// Sign in & up API
|
||||
app.post("/api/user/login", loginHandler)
|
||||
|
||||
/**
|
||||
* APIs
|
||||
*/
|
||||
// Google Sheets API
|
||||
app.get("/JeuJavListGet", jeuJavListGet)
|
||||
app.get("/EnvieListGet", envieListGet)
|
||||
app.post("/EnvieAdd", envieAdd)
|
||||
|
||||
// Secured APIs
|
||||
app.get("/MembreGet", secure as RequestHandler, membreGet)
|
||||
app.post("/MembreSet", secure as RequestHandler, membreSet)
|
||||
|
||||
// Use React server-side rendering middleware
|
||||
app.get("*", ssr)
|
||||
|
||||
|
58
src/server/secure.ts
Normal file
58
src/server/secure.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
import { NextFunction, Request, Response } from "express"
|
||||
import path from "path"
|
||||
import { promises as fs } from "fs"
|
||||
import { verify, sign } from "jsonwebtoken"
|
||||
import { canonicalEmail } from "../utils/standardization"
|
||||
|
||||
import config from "../config"
|
||||
|
||||
type AuthorizedRequest = Request & { headers: { authorization: string } }
|
||||
|
||||
let cachedSecret: string
|
||||
getSecret() // Necessary until we can make async express middleware
|
||||
|
||||
export function secure(request: AuthorizedRequest, response: Response, next: NextFunction): void {
|
||||
if (!cachedSecret) {
|
||||
response.status(408).json({
|
||||
error: "Server still loading",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const rawToken = request.headers.authorization
|
||||
const token = rawToken && rawToken.split(/\s/)[1]
|
||||
|
||||
verify(token, cachedSecret, (tokenError: any, decoded: any) => {
|
||||
if (tokenError) {
|
||||
response.status(403).json({
|
||||
error: "Invalid token, please Log in first, criterion auth",
|
||||
})
|
||||
return
|
||||
}
|
||||
decoded.user.replace(/@gmailcom$/, "@gmail.com")
|
||||
response.locals.jwt = decoded
|
||||
next()
|
||||
})
|
||||
}
|
||||
|
||||
async function getSecret() {
|
||||
if (!cachedSecret) {
|
||||
const SECRET_PATH = path.resolve(process.cwd(), "access/jwt_secret.json")
|
||||
|
||||
try {
|
||||
const secretContent = await fs.readFile(SECRET_PATH)
|
||||
cachedSecret = secretContent && JSON.parse(secretContent.toString()).secret
|
||||
} catch (e: any) {
|
||||
cachedSecret = config.JWT_SECRET
|
||||
}
|
||||
}
|
||||
|
||||
return cachedSecret
|
||||
}
|
||||
|
||||
export async function getJwt(email: string): Promise<string> {
|
||||
const jwt = sign({ user: canonicalEmail(email), permissions: [] }, await getSecret(), {
|
||||
expiresIn: "7d",
|
||||
})
|
||||
return jwt
|
||||
}
|
@@ -2,6 +2,7 @@ import { Request, Response, NextFunction } from "express"
|
||||
import bcrypt from "bcrypt"
|
||||
import { Membre, MemberLogin, emailRegexp, passwordMinLength } from "../../services/membres"
|
||||
import getAccessors from "../gsheets/accessors"
|
||||
import { getJwt } from "../secure"
|
||||
|
||||
const { listGet } = getAccessors("Membres", new Membre())
|
||||
|
||||
@@ -50,9 +51,12 @@ export async function login(rawEmail: string, rawPassword: string): Promise<Memb
|
||||
throw Error("Mauvais mot de passe pour cet email")
|
||||
}
|
||||
|
||||
const jwt = await getJwt(email)
|
||||
|
||||
return {
|
||||
membre: {
|
||||
prenom: membre.prenom,
|
||||
},
|
||||
jwt,
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import axios from "axios"
|
||||
|
||||
import config from "../config"
|
||||
import { axiosConfig } from "./auth"
|
||||
|
||||
export type ElementWithId = unknown & { id: number }
|
||||
|
||||
@@ -15,6 +16,7 @@ export function get<Element>(elementName: string): (id: number) => Promise<{
|
||||
return async (id: number): Promise<ElementGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.get(`${config.API_URL}/${elementName}Get`, {
|
||||
...axiosConfig,
|
||||
params: { id },
|
||||
})
|
||||
return { data }
|
||||
@@ -34,7 +36,7 @@ export function listGet<Element>(elementName: string): () => Promise<{
|
||||
}
|
||||
return async (): Promise<ElementListGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.get(`${config.API_URL}/${elementName}ListGet`)
|
||||
const { data } = await axios.get(`${config.API_URL}/${elementName}ListGet`, axiosConfig)
|
||||
return { data }
|
||||
} catch (error) {
|
||||
return { error: error as Error }
|
||||
@@ -57,7 +59,8 @@ export function add<ElementNoId extends object, Element extends ElementNoId & El
|
||||
try {
|
||||
const { data } = await axios.post(
|
||||
`${config.API_URL}/${elementName}Add`,
|
||||
membreWithoutId
|
||||
membreWithoutId,
|
||||
axiosConfig
|
||||
)
|
||||
return { data }
|
||||
} catch (error) {
|
||||
@@ -76,7 +79,11 @@ export function set<Element>(elementName: string): (membre: Element) => Promise<
|
||||
}
|
||||
return async (membre: Element): Promise<ElementGetResponse> => {
|
||||
try {
|
||||
const { data } = await axios.post(`${config.API_URL}/${elementName}Set`, membre)
|
||||
const { data } = await axios.post(
|
||||
`${config.API_URL}/${elementName}Set`,
|
||||
membre,
|
||||
axiosConfig
|
||||
)
|
||||
return { data }
|
||||
} catch (error) {
|
||||
return { error: error as Error }
|
||||
|
22
src/services/auth.ts
Normal file
22
src/services/auth.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { AxiosRequestConfig } from "axios"
|
||||
|
||||
const storage: any = localStorage
|
||||
|
||||
const jwt: string | null = storage?.getItem("id_token")
|
||||
if (jwt) {
|
||||
setJWT(jwt)
|
||||
}
|
||||
|
||||
export const axiosConfig: AxiosRequestConfig = {
|
||||
headers: {},
|
||||
}
|
||||
|
||||
export function setJWT(token: string): void {
|
||||
axiosConfig.headers.Authorization = `Bearer ${token}`
|
||||
storage?.setItem("id_token", token)
|
||||
}
|
||||
|
||||
export function unsetJWT(): void {
|
||||
delete axiosConfig.headers.Authorization
|
||||
storage?.removeItem("id_token")
|
||||
}
|
@@ -36,6 +36,7 @@ export interface MemberLogin {
|
||||
membre?: {
|
||||
prenom: string
|
||||
}
|
||||
jwt?: string
|
||||
error?: string
|
||||
}
|
||||
|
||||
|
18
src/utils/standardization.ts
Normal file
18
src/utils/standardization.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
export function canonicalEmail(email: string): string {
|
||||
email = email.replace(/^\s+|\s+$/g, "")
|
||||
if (/@gmail.com$/.test(email)) {
|
||||
let domain = email.replace(/^.*@/, "")
|
||||
domain = domain.replace(/^googlemail%.com$/, "gmail.com")
|
||||
email = email
|
||||
.replace(/\./g, "")
|
||||
.replace(/^[^@]+/, (match) => match.toLowerCase())
|
||||
.replace(/@.*$/, `@${domain}`)
|
||||
}
|
||||
return email.toLowerCase()
|
||||
}
|
||||
|
||||
export function validEmail(email: string): boolean {
|
||||
return /^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/.test(
|
||||
email
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user