Backend API looks fine

This commit is contained in:
Eric van der Vlist 2023-03-03 15:57:15 +01:00
parent c6951c0838
commit 562485134a
9 changed files with 266 additions and 20 deletions

139
package-lock.json generated
View File

@ -12,6 +12,8 @@
"@kobalte/core": "^0.6.2", "@kobalte/core": "^0.6.2",
"@types/pouchdb": "^6.4.0", "@types/pouchdb": "^6.4.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nodemailer": "^6.9.1",
"nodemailer-express-handlebars": "^6.0.0",
"pouchdb": "^8.0.1", "pouchdb": "^8.0.1",
"pouchdb-server": "^4.2.0", "pouchdb-server": "^4.2.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",
@ -23,6 +25,8 @@
"@solidjs/testing-library": "^0.6.1", "@solidjs/testing-library": "^0.6.1",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/nodemailer": "^6.4.7",
"@types/nodemailer-express-handlebars": "^4.0.2",
"@types/testing-library__jest-dom": "^5.14.5", "@types/testing-library__jest-dom": "^5.14.5",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"@vitest/coverage-c8": "^0.29.2", "@vitest/coverage-c8": "^0.29.2",
@ -2948,6 +2952,12 @@
"integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==", "integrity": "sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==",
"dev": true "dev": true
}, },
"node_modules/@types/express-handlebars": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/@types/express-handlebars/-/express-handlebars-5.3.1.tgz",
"integrity": "sha512-DSzaERLO4gHb8AqnrL58jzSDyT0yDdl6HqDc+bGz1Hf0nrG1FK30nHGzv8NBEGR8QV9eUGB/YaE0Qj3NjF7siw==",
"dev": true
},
"node_modules/@types/istanbul-lib-coverage": { "node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.4", "version": "2.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz",
@ -3031,6 +3041,25 @@
"integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==", "integrity": "sha512-5EWrvLmglK+imbCJY0+INViFWUHg1AHel1sq4ZVSfdcNqGy9Edv3UB9IIzzg+xPaUcAgZYcfVs2fBcwDeZzU0A==",
"dev": true "dev": true
}, },
"node_modules/@types/nodemailer": {
"version": "6.4.7",
"resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz",
"integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/nodemailer-express-handlebars": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/nodemailer-express-handlebars/-/nodemailer-express-handlebars-4.0.2.tgz",
"integrity": "sha512-LnKnqgl6C3osQKQDcIxB6P4iS2Iixq+p0ZCC93pzhSRLvo4PgGHmrTFE38ZGrFxc/DyZefYGEsYHSblxJtwuxw==",
"dev": true,
"dependencies": {
"@types/express-handlebars": "^5",
"@types/nodemailer": "*"
}
},
"node_modules/@types/pouchdb": { "node_modules/@types/pouchdb": {
"version": "6.4.0", "version": "6.4.0",
"resolved": "https://registry.npmjs.org/@types/pouchdb/-/pouchdb-6.4.0.tgz", "resolved": "https://registry.npmjs.org/@types/pouchdb/-/pouchdb-6.4.0.tgz",
@ -5497,6 +5526,56 @@
"node": ">= 0.10.0" "node": ">= 0.10.0"
} }
}, },
"node_modules/express-handlebars": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/express-handlebars/-/express-handlebars-6.0.7.tgz",
"integrity": "sha512-iYeMFpc/hMD+E6FNAZA5fgWeXnXr4rslOSPkeEV6TwdmpJ5lEXuWX0u9vFYs31P2MURctQq2batR09oeNj0LIg==",
"dependencies": {
"glob": "^8.1.0",
"graceful-fs": "^4.2.10",
"handlebars": "^4.7.7"
},
"engines": {
"node": ">=v12.22.9"
}
},
"node_modules/express-handlebars/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/express-handlebars/node_modules/glob": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
"integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^5.0.1",
"once": "^1.3.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/express-handlebars/node_modules/minimatch": {
"version": "5.1.6",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
"integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=10"
}
},
"node_modules/express-pouchdb": { "node_modules/express-pouchdb": {
"version": "4.2.0", "version": "4.2.0",
"resolved": "https://registry.npmjs.org/express-pouchdb/-/express-pouchdb-4.2.0.tgz", "resolved": "https://registry.npmjs.org/express-pouchdb/-/express-pouchdb-4.2.0.tgz",
@ -5832,8 +5911,7 @@
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
"devOptional": true
}, },
"node_modules/fsevents": { "node_modules/fsevents": {
"version": "2.3.2", "version": "2.3.2",
@ -6058,6 +6136,26 @@
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz",
"integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="
}, },
"node_modules/handlebars": {
"version": "4.7.7",
"resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.7.tgz",
"integrity": "sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA==",
"dependencies": {
"minimist": "^1.2.5",
"neo-async": "^2.6.0",
"source-map": "^0.6.1",
"wordwrap": "^1.0.0"
},
"bin": {
"handlebars": "bin/handlebars"
},
"engines": {
"node": ">=0.4.7"
},
"optionalDependencies": {
"uglify-js": "^3.1.4"
}
},
"node_modules/has": { "node_modules/has": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
@ -7687,6 +7785,11 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/neo-async": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
"integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.6.7", "version": "2.6.7",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
@ -7784,6 +7887,25 @@
"integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==",
"dev": true "dev": true
}, },
"node_modules/nodemailer": {
"version": "6.9.1",
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.1.tgz",
"integrity": "sha512-qHw7dOiU5UKNnQpXktdgQ1d3OFgRAekuvbJLcdG5dnEo/GtcTHRYM7+UfJARdOFU9WUQO8OiIamgWPmiSFHYAA==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/nodemailer-express-handlebars": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/nodemailer-express-handlebars/-/nodemailer-express-handlebars-6.0.0.tgz",
"integrity": "sha512-xo5nVCn2GDaE8o0ppOWVq0s3Tt5xLn+R2pDROQrZALKCoP6WsEOXrNGQwH/tYJ4vucWtWd+zdcHm734S6CSA7w==",
"dependencies": {
"express-handlebars": "^6.0.0"
},
"engines": {
"node": "14.* || 16.* || >= 18"
}
},
"node_modules/noop-fn": { "node_modules/noop-fn": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/noop-fn/-/noop-fn-1.0.0.tgz", "resolved": "https://registry.npmjs.org/noop-fn/-/noop-fn-1.0.0.tgz",
@ -9996,7 +10118,6 @@
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@ -10508,6 +10629,18 @@
"integrity": "sha512-LQc2s/ZDMaCN3QLpa+uzHUOQ7SdV0qgv3VBXOolQGXTaaZpIur6PwUclF5nN2hNkiTRcUugXd1zFOW3FLJ135Q==", "integrity": "sha512-LQc2s/ZDMaCN3QLpa+uzHUOQ7SdV0qgv3VBXOolQGXTaaZpIur6PwUclF5nN2hNkiTRcUugXd1zFOW3FLJ135Q==",
"dev": true "dev": true
}, },
"node_modules/uglify-js": {
"version": "3.17.4",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz",
"integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==",
"optional": true,
"bin": {
"uglifyjs": "bin/uglifyjs"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/uid-safe": { "node_modules/uid-safe": {
"version": "2.1.5", "version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",

View File

@ -16,6 +16,8 @@
"@solidjs/testing-library": "^0.6.1", "@solidjs/testing-library": "^0.6.1",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@types/lodash": "^4.14.191", "@types/lodash": "^4.14.191",
"@types/nodemailer": "^6.4.7",
"@types/nodemailer-express-handlebars": "^4.0.2",
"@types/testing-library__jest-dom": "^5.14.5", "@types/testing-library__jest-dom": "^5.14.5",
"@types/uuid": "^9.0.1", "@types/uuid": "^9.0.1",
"@vitest/coverage-c8": "^0.29.2", "@vitest/coverage-c8": "^0.29.2",
@ -36,6 +38,8 @@
"@kobalte/core": "^0.6.2", "@kobalte/core": "^0.6.2",
"@types/pouchdb": "^6.4.0", "@types/pouchdb": "^6.4.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"nodemailer": "^6.9.1",
"nodemailer-express-handlebars": "^6.0.0",
"pouchdb": "^8.0.1", "pouchdb": "^8.0.1",
"pouchdb-server": "^4.2.0", "pouchdb-server": "^4.2.0",
"tippy.js": "^6.3.7", "tippy.js": "^6.3.7",

View File

@ -1,7 +1,10 @@
import { adminCredentials } from '~/components/credentials'; import { adminCredentials } from '~/components/credentials';
export const getUrl = (id: string, db = 'dyomedea_users') => { export const getUrl = (
const credentials = adminCredentials(); id: string,
db = 'dyomedea_users',
credentials = adminCredentials()
) => {
if (!credentials) { if (!credentials) {
return ''; return '';
} }

View File

@ -2,19 +2,24 @@ import { adminCredentials } from '~/components/credentials';
import { getUrl } from './get-url'; import { getUrl } from './get-url';
import { headersWithAuth } from './headers-with-auth'; import { headersWithAuth } from './headers-with-auth';
export const get = async (id: string, db = 'dyomedea_users') => { export const get = async (
const credentials = adminCredentials(); id: string,
db = 'dyomedea_users',
credentials = adminCredentials()
) => {
if (!credentials) { if (!credentials) {
return null; return null;
} }
const { database } = credentials; const { database } = credentials;
const headers = headersWithAuth(); const headers = headersWithAuth(credentials);
if (!headers) { if (!headers) {
return null; return null;
} }
const response = await fetch(getUrl(id, db), { // console.log({ caller: 'get', id, db, credentials, headers });
const response = await fetch(getUrl(id, db, credentials), {
method: 'GET', method: 'GET',
mode: 'cors', mode: 'cors',
headers, headers,

12
src/lib/obfuscate-mail.ts Normal file
View File

@ -0,0 +1,12 @@
export const obfuscateMail = (mail: string) => {
const [user, hostname] = mail.split('@');
const obfuscatedUser =
user.length < 4
? user[0] + new Array(user.length).join('.')
: user[0] + new Array(user.length - 1).join('.') + user[user.length - 1];
const obfuscatedHostname =
hostname[0] +
new Array(hostname.length - 1).join('.') +
hostname[hostname.length - 1];
return obfuscatedUser + '@' + obfuscatedHostname;
};

View File

@ -8,36 +8,46 @@ export const put = async (
content: any, content: any,
isNew: boolean = false, isNew: boolean = false,
db = 'dyomedea_users', db = 'dyomedea_users',
overwrite_ = false overwrite_ = false,
credentials = adminCredentials()
) => { ) => {
const credentials = adminCredentials(); console.log({ caller: 'put', id, isNew, db, content, credentials });
if (!credentials) { if (!credentials) {
return null; return null;
} }
const { database } = credentials; const { database } = credentials;
if (!isNew) { if (!isNew) {
const previous = await get(id, db); const previous = await get(id, db, credentials);
// console.log({
// caller: 'put / after get',
// id,
// isNew,
// db,
// content,
// previous,
// });
if (!!previous) { if (!!previous) {
content._rev = previous._rev; content._rev = previous._rev;
if (!overwrite_) { if (!overwrite_) {
content.__ = previous.__; content.$ = previous.$;
} }
} }
} }
const headers = headersWithAuth(); const headers = headersWithAuth(credentials);
if (!headers) { if (!headers) {
return null; return null;
} }
headers.set('Content-type', 'application/json; charset=UTF-8'); headers.set('Content-type', 'application/json; charset=UTF-8');
const response = await fetch(getUrl(id, db), { const response = await fetch(getUrl(id, db, credentials), {
method: 'PUT', method: 'PUT',
mode: 'cors', mode: 'cors',
headers, headers,
body: JSON.stringify(content), body: JSON.stringify(content),
}); });
console.log({ caller: 'put', id, isNew, db, status: response.status }); // console.log({ caller: 'put', id, isNew, db, status: response.status });
return await response.json(); return await response.json();
}; };

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Validation</title>
</head>
<body>
<h2>Bonjour {{id}}! </h2>
<p>Le code de validation de votre configuration pour l'application Dyomedea est <code>{{code}}</code>.</p>
<p>Ce code est valable pendant quinze minutes.</p>
<p>Eric</p>
</body>
</html>

View File

@ -1,6 +1,22 @@
import { APIEvent, json } from 'solid-start/api'; import { APIEvent, json } from 'solid-start/api';
import { findUserByToken } from '~/lib/find-user-by-token';
import { readConfig } from '~/server-only-lib/read-config';
export async function GET({ params, env }: APIEvent) { export async function GET({ params, env }: APIEvent) {
console.log({ caller: 'api/conf GET', params }); const { credentials } = readConfig();
return json({ params, env, response: 'OK' }); const user = await findUserByToken(params.token, credentials);
console.log({ caller: 'api/conf GET', params, credentials, user });
if (!user) {
return json({ params, response: 'NOT FOUND' });
}
const { code, expiration } = user.$;
if (
!code ||
!expiration ||
params.code !== code ||
new Date() > new Date(expiration)
) {
return json({ params, response: 'NOT FOUND' });
}
return json({ params, response: 'OK', user });
} }

View File

@ -1,11 +1,60 @@
import { APIEvent, json } from 'solid-start/api'; import { APIEvent, json } from 'solid-start/api';
import { findUserByToken } from '~/lib/find-user-by-token'; import { findUserByToken } from '~/lib/find-user-by-token';
import { readConfig } from '~/server-only-lib/read-config'; import { readConfig } from '~/server-only-lib/read-config';
import { createTransport } from 'nodemailer';
import { resolve } from 'path';
import hbs from 'nodemailer-express-handlebars';
import { put } from '~/lib/put';
import { obfuscateMail } from '~/lib/obfuscate-mail';
export async function GET({ params }: APIEvent) { export async function GET({ params }: APIEvent) {
const { credentials } = readConfig(); const { credentials, mailer } = readConfig();
console.log({ caller: 'api/conf GET', params, credentials }); console.log({ caller: 'api/conf GET', params, credentials });
const user = await findUserByToken(params.token, credentials); const user = await findUserByToken(params.token, credentials);
console.log({ caller: 'api/conf GET', params, credentials, user }); console.log({ caller: 'api/conf GET', params, credentials, user });
return json({ params, response: 'OK' }); if (!user) {
return json({ params, response: 'NOT FOUND' });
}
const transporter = createTransport(mailer);
const handlebarOptions = {
viewEngine: {
partialsDir: resolve('src/mail-template/'),
defaultLayout: false,
},
viewPath: resolve('src/mail-template/'),
};
transporter.use('compile', hbs(handlebarOptions));
const code = [...Array(6)].map((_) => (Math.random() * 10) | 0).join('');
const mailOptions = {
from: '"Dyomedea app" <app@dyomedea.com>', // sender address
to: user.mail, // list of receivers
subject: 'Validation!',
template: 'email', // the name of the template file i.e email.handlebars
context: {
id: user._id,
code,
},
};
await transporter.sendMail(mailOptions);
await put(
user._id,
{
...user,
$: {
code,
expiration: new Date(Date.now() + 15 * 60 * 1000).toISOString(),
},
},
false,
undefined,
true,
credentials
);
return json({ params, status: 'OK', mail: obfuscateMail(user.mail) });
} }