Commit 15de9d3c authored by Emmanuel Raviart's avatar Emmanuel Raviart
Browse files

Migrate middlewares to Svelte Kit.

parent 46bc19d0
......@@ -50,7 +50,7 @@ npm install
As normal user, compile scripts to be able to use them:
```bash
npm run build-scripts
npm run dist
```
As normal user, create database tables:
......
This diff is collapsed.
......@@ -10,13 +10,13 @@
"author": "DBnomics Team",
"scripts": {
"build": "svelte-kit build",
"build-scripts": "npm run build-scripts:lib && npm run build-scripts:tsc",
"build-scripts:lib": "mkdir -p dist/node_modules && cd dist/node_modules/ && rm -f '$lib' && ln -s ../lib/ '$lib'",
"build-scripts:tsc": "tsc --declaration --outDir dist --rootDir src",
"check": "svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"configure": "node --experimental-specifier-resolution=node dist/scripts/configure.js",
"dev": "svelte-kit dev",
"dev": "node --experimental-specifier-resolution=node node_modules/.bin/svelte-kit dev",
"dist": "npm run dist:lib && npm run dist:tsc",
"dist:lib": "mkdir -p node_modules && cd node_modules/ && rm -f '$lib' && ln -s ../dist/lib/ '$lib'",
"dist:tsc": "tsc --declaration --outDir dist --rootDir src",
"format": "prettier --write --plugin-search-dir=. .",
"lint": "prettier --check --plugin-search-dir=. . && eslint --ignore-path .gitignore .",
"postinstall": "patch-package",
......@@ -27,6 +27,7 @@
"fast-xml-parser": "^3.17.5",
"he": "^1.2.0",
"node-stream-zip": "^1.14.0",
"on-headers": "^1.0.2",
"slug": "^5.0.1",
"ws": "^8.1.0"
},
......@@ -45,6 +46,8 @@
"@types/fs-extra": "^9.0.12",
"@types/he": "^1.1.2",
"@types/node-fetch": "^2.5.12",
"@types/passport": "^1.0.7",
"@types/passport-local": "^1.0.34",
"@types/slug": "^5.0.2",
"@types/ws": "^7.4.7",
"@typescript-eslint/eslint-plugin": "^4.19.0",
......@@ -52,12 +55,14 @@
"a17t": "^0.5.1",
"autoprefixer": "^10.2.5",
"cached-iterable": "^0.3.0",
"connect-pg-simple": "^6.2.1",
"cssnano": "^5.0.1",
"dedent-js": "^1.0.1",
"dotenv": "^10.0.0",
"eslint": "^7.22.0",
"eslint-config-prettier": "^8.1.0",
"eslint-plugin-svelte3": "^3.2.0",
"express-session": "^1.17.2",
"fa-svelte": "^3.1.0",
"front-matter": "^4.0.2",
"fs-extra": "^10.0.0",
......@@ -65,7 +70,9 @@
"mdast-util-gfm-table": "^1.0.1",
"micromark-extension-gfm-table": "^1.0.0",
"node-fetch": "^2.6.1",
"openid-client": "^4.7.4",
"passport": "^0.4.1",
"passport-local": "^1.0.0",
"patch-package": "^6.4.7",
"pg-native": "^3.0.0",
"pg-promise": "^10.11.0",
......@@ -74,6 +81,7 @@
"prettier": "^2.3.2",
"prettier-plugin-svelte": "^2.2.0",
"sanitize-filename": "^1.6.3",
"sirv": "^1.0.14",
"sockette": "^2.0.6",
"svelte": "^3.34.0",
"svelte-check": "^2.0.0",
......
import type { GetSession } from "@sveltejs/kit"
import type { GetSession, Handle } from "@sveltejs/kit"
import { execSync } from "child_process"
import dedent from "dedent-js"
import config from "$lib/server/config"
import { db } from "$lib/server/database"
import type { ZammadUser } from "$lib/server/zammad"
import type { Session } from "$lib/sessions"
import { version } from "../package.json"
const {
baseUrl,
devAuthentication,
openIdConnect,
proxy,
publicDataDir,
secure,
sessionSecret,
title,
webSocketBaseUrl,
zammad,
} = config
const branch = execSync("git rev-parse --abbrev-ref HEAD", {
env: process.env,
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
}).replace(/[\n\r]+$/, "")
const commit = execSync("git rev-parse HEAD", {
env: process.env,
encoding: "utf-8",
stdio: ["ignore", "pipe", "pipe"],
}).substring(0, 8)
const { baseUrl, title, webSocketBaseUrl, zammad } = config
export const getSession: GetSession<{}, Session> = async (request) => {
const headers = request.headers
const language = headers["x-language"]
delete headers["x-language"]
const user = headers["x-user"] as unknown as ZammadUser | undefined
delete headers["x-user"]
// const session = req.session
// const currentLanguage = session.language ?? Language.Fr
// language.set(currentLanguage)
// const user = req.user as ZammadUser | undefined
let cart = undefined
// if (user !== undefined) {
// const entry = await db.oneOrNone(
// dedent`
// SELECT paths FROM carts
// WHERE id = $<userId>
// `,
// {
// userId: user.id,
// },
// )
// if (entry?.paths != null) {
// cart = entry.paths
// }
// }
if (user !== undefined) {
const entry = await db.oneOrNone(
dedent`
SELECT paths FROM carts
WHERE id = $<userId>
`,
{
userId: user.id,
},
)
if (entry?.paths != null) {
cart = entry.paths
}
}
return {
baseUrl,
// branch,
branch,
cart: cart ?? [],
// commit,
commit,
// expires: session.cookie?.expires ?? null,
// language: currentLanguage,
language,
title: title,
// user,
// version,
user,
version,
webSocketBaseUrl,
zammadUrl: zammad.url,
}
......
import assert from "assert"
import dedent from "dedent-js"
import type { IConnected } from "pg-promise"
// The use of "require" instead of "import" for "pg-promise" is needed:
// - to remove circular dependencies:
// * node_modules/readable-stream/lib/_stream_readable.js
// -> node_modules/readable-stream/lib/_stream_duplex.js
// -> node_modules/readable-stream/lib/_stream_readable.js
// * node_modules/pg/lib/index.js
// -> node_modules/pg-pool/index.js
// -> node_modules/pg/lib/index.js
// - and to avoid:
// TypeError [ERR_INVALID_ARG_TYPE]: The "superCtor" argument must be
// of type function. Received undefined
import pgPromiseFactory from "pg-promise"
import type { IClient } from "pg-promise/typescript/pg-subset"
......
......@@ -38,6 +38,6 @@ export const put: RequestHandler = async ({
return respondInvalidBody(requestPath, body, bodyError)
}
const { language } = body
req.session.language = language
return { status: 204 }
// req.session.language = language
return { status: 204, headers: { "x-language": language } }
}
import {
Audit,
auditHttpUrl,
auditRequire,
auditSetNullish,
auditTest,
} from "@auditors/core"
import type { RequestHandler } from "@sveltejs/kit"
import passport from "passport"
import { auditQuerySingleton } from "$lib/auditors/queries"
import config from "$lib/server/config"
import type { NextFunction, Request, Response } from "../../requests_responses"
import type { ZammadUser } from "$lib/server/zammad"
const audit = new Audit({
filterExtra: false,
simplify: true,
})
const { baseUrl, devAuthentication, openIdConnect } = config
export async function get(req: Request, res: Response, next: NextFunction) {
const [query, queryError] = auditLoginQuery(audit, query)
if (queryError !== null) {
console.error(
`Error in ${path} query:\n${JSON.stringify(
query,
null,
2,
)}\n\nError:\n${JSON.stringify(queryError, null, 2)}`,
)
res.writeHead(400, {
"Content-Type": "application/json; charset=utf-8",
})
return res.end(
JSON.stringify(
{
...query,
error: {
code: 400,
details: queryError,
message: "Invalid query",
path: path,
},
},
null,
2,
),
)
}
const { redirect: redirectUrl } = query
if (openIdConnect != null) {
req.session!.redirectUrl = redirectUrl
passport.authenticate(new URL(openIdConnect.issuerUrl).hostname, {
// Parameters added to authentication request URL (in query).
})(req, res, next)
} else if (devAuthentication != null) {
const authenticationCallback = async (
err: any,
user: ZammadUser,
_info: any,
) => {
// console.log("auth/login: local login authenticationCallback", err, user, info)
if (err) {
return next(err)
}
if (user == null) {
res.writeHead(302, {
Location: redirectUrl,
})
return res.end()
}
req.login(user, function (err) {
if (err) {
return next(err)
}
res.writeHead(302, {
Location: redirectUrl,
})
return res.end()
})
}
req.body = {
password: "UNUSED",
email: devAuthentication.email,
}
passport.authenticate("local", authenticationCallback)(req, res, next)
} else {
res.writeHead(400, {
"Content-Type": "text/plain; charset=utf-8",
})
return res.end("No authentication method configured!")
}
}
function auditLoginQuery(
audit: Audit,
query: URLSearchParams,
): [unknown, unknown] {
if (query == null) {
return [query, null]
}
if (!(query instanceof URLSearchParams)) {
return audit.unexpectedType(query, "URLSearchParams")
}
const data: { [key: string]: unknown } = {}
// @ts-expect-error: urlSearchParams.entries() exists both on browsers & Node.
for (const [key, value] of query.entries()) {
let values = data[key] as string[] | undefined
if (values === undefined) {
values = data[key] = []
}
values.push(value)
}
const errors: { [key: string]: unknown } = {}
const remainingKeys = new Set(Object.keys(data))
audit.attribute(
data,
"redirect",
true,
errors,
remainingKeys,
auditQuerySingleton(
auditHttpUrl,
auditTest((url: string) => url.startsWith(baseUrl)),
auditRequire,
),
)
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
}
import type { RequestHandler } from "@sveltejs/kit"
import passport from "passport"
import config from "$lib/server/config"
import type { NextFunction, Request, Response } from "../../requests_responses"
import type { ZammadUser } from "$lib/server/zammad"
const { baseUrl, openIdConnect } = config
export function get(req: Request, res: Response, next: NextFunction) {
let redirectUrl = req.session?.redirectUrl
if (redirectUrl === undefined) {
redirectUrl = baseUrl
} else {
delete req.session!.redirectUrl
}
const authenticationCallback = async (
err: any,
user: ZammadUser,
_info: any,
) => {
// console.log(
// "auth/login_callback: passport.authenticate callback",
// err,
// user,
// info,
// )
if (err) {
return next(err)
}
if (user == null) {
res.writeHead(302, {
Location: redirectUrl,
})
return res.end()
}
req.login(user, function (err) {
if (err) {
return next(err)
}
res.writeHead(302, {
Location: redirectUrl,
})
return res.end()
})
}
passport.authenticate(
new URL(openIdConnect.issuerUrl).hostname,
authenticationCallback,
)(req, res, next)
}
import type { RequestHandler } from "@sveltejs/kit"
import {
Audit,
auditHttpUrl,
auditRequire,
auditSetNullish,
auditTest,
} from "@auditors/core"
import passport from "passport"
import { auditQuerySingleton } from "$lib/auditors/queries"
import config from "$lib/server/config"
import type { Request, Response } from "../../requests_responses"
import type { ZammadUser } from "$lib/server/zammad"
const audit = new Audit({
filterExtra: false,
simplify: true,
})
const { baseUrl, openIdConnect } = config
export function get(req: Request, res: Response) {
const [query, queryError] = auditLogoutQuery(audit, query)
if (queryError !== null) {
console.error(
`Error in ${path} query:\n${JSON.stringify(
query,
null,
2,
)}\n\nError:\n${JSON.stringify(queryError, null, 2)}`,
)
res.writeHead(400, {
"Content-Type": "application/json; charset=utf-8",
})
return res.end(
JSON.stringify(
{
...query,
error: {
code: 400,
details: queryError,
message: "Invalid query",
path: path,
},
},
null,
2,
),
)
}
const { redirect: redirectUrl } = query
const user = locals.user as ZammadUser | undefined
req.logout()
if (openIdConnect != null) {
if (user?.idToken == null) {
console.log("logout: User has no id_token.")
res.writeHead(302, {
Location: redirectUrl,
})
return res.end()
}
req.session!.redirectUrl = redirectUrl
const openIdConnectClient = (passport as any)._strategies[
new URL(openIdConnect.issuerUrl).hostname
]._client
const endSessionUrl = openIdConnectClient.endSessionUrl({
id_token_hint: user.idToken, // profil.id_token,
})
res.writeHead(302, {
Location: endSessionUrl,
})
return res.end()
} else {
res.writeHead(302, {
Location: redirectUrl,
})
return res.end()
}
}
function auditLogoutQuery(
audit: Audit,
query: URLSearchParams,
): [unknown, unknown] {
if (query == null) {
return [query, null]
}
if (!(query instanceof URLSearchParams)) {
return audit.unexpectedType(query, "URLSearchParams")
}
const data: { [key: string]: unknown } = {}
// @ts-expect-error: urlSearchParams.entries() exists both on browsers & Node.
for (const [key, value] of query.entries()) {
let values = data[key] as string[] | undefined
if (values === undefined) {
values = data[key] = []
}
values.push(value)
}
const errors: { [key: string]: unknown } = {}
const remainingKeys = new Set(Object.keys(data))
audit.attribute(
data,
"redirect",
true,
errors,
remainingKeys,
auditQuerySingleton(
auditHttpUrl,
auditTest((url: string) => url.startsWith(baseUrl)),
auditRequire,
),
)
return audit.reduceRemaining(data, errors, remainingKeys, auditSetNullish({}))
}
import type { RequestHandler } from "@sveltejs/kit"
import config from "$lib/server/config"
import type { Request, Response } from "../../requests_responses"
const { baseUrl } = config
export function get(req: Request, res: Response) {
let redirectUrl = req.session?.redirectUrl
if (redirectUrl === undefined) {
redirectUrl = baseUrl
} else {
delete req.session!.redirectUrl
}
return {
status: 302,
headers: {
Location: redirectUrl,
},
}
}
import bodyParser from "body-parser"
import { execSync } from "child_process"
import compression from "compression"
// The use of "require" instead of "import" for "connect-pg-simple" is needed:
// - to remove circular dependencies:
// * node_modules/readable-stream/lib/_stream_readable.js
// -> node_modules/readable-stream/lib/_stream_duplex.js
// -> node_modules/readable-stream/lib/_stream_readable.js
// * node_modules/pg/lib/index.js
// -> node_modules/pg-pool/index.js
// -> node_modules/pg/lib/index.js
// - and to avoid:
// TypeError [ERR_INVALID_ARG_TYPE]: The "superCtor" argument must be
// of type function. Received undefined
const PgSession = require("connect-pg-simple")
import dedent from "dedent-js"
// import bodyParser from "body-parser"
import express from "express"
import session from "express-session"
import http from "http"
import { Headers } from "node-fetch"
// The use of "require" instead of "import" for "openid-client" is needed,
// to avoid build error:
// 'default' is not exported by node_modules/openid-client/lib/index.js,
// imported by node_modules/openid-client/lib/index.mjs
const openIdClient = require("openid-client")
import passport from "passport"
import { Strategy as LocalStrategy } from "passport-local"
import sirv from "sirv"
import * as sapper from "@sapper/server"
import { version } from "../package.json"
import config from "$lib/server/config"
import { Language } from "$lib/data"
import { connectDb, db } from "$lib/server/database"
import type { Request, Response } from "./requests_responses"
import { language } from "$lib/stores"
import { slugify } from "$lib/strings"
import { facetsWebSocketServer } from "./web_sockets/facets"
import type { ZammadUser } from "$lib/server/zammad"
import { fetchZammadApi, fetchZammadApiList } from "$lib/server/zammad"
const {
custom: openIdConnectCustom,
Issuer: OpenIdConnectIssuer,
Strategy: OpenIdConnectStrategy,
} = openIdClient
openIdConnectCustom.setHttpOptionsDefaults({
timeout: 10000, // 10 s
})
const {
baseUrl,
devAuthentication,
devUser,
openIdConnect,
proxy,
publicDataDir,
secure,
sessionSecret,
title,
webSocketBaseUrl,
zammad,
} = config
const { proxy, webSocketBaseUrl } = config
const { PORT } = process.env