diff --git a/README.md b/README.md index aea0feb..f2e1d0a 100644 --- a/README.md +++ b/README.md @@ -10,21 +10,21 @@ Код приложения лежит в папке `spa`, собранная версия уже лежит в папке `spa/build`. Для выполнения задания трогать код приложения не потребуется, но если захочешь что-то поменять, не забудь установить зависимости (`npm install` в папке `spa`) и собрать новую версию приложения (`npm run build`). -0. Поставь зависимости и запусти сервер. +--0. Поставь зависимости и запусти сервер. - Для этого перейди в директорию задачи и выполни команду `npm install`. - После установки зависимостей, выполни команду `npm run start`. - После запуска, перейди по адресу [localhost:3000](http://localhost:3000) -1. Сделай так, чтобы сервер смог отдавать статические файлы из директории `spa/build`. В express для этого есть middleware `express.static`. Подробнее можно прочитать [здесь](https://expressjs.com/en/starter/static-files.html) +--1. Сделай так, чтобы сервер смог отдавать статические файлы из директории `spa/build`. В express для этого есть middleware `express.static`. Подробнее можно прочитать [здесь](https://expressjs.com/en/starter/static-files.html) -2. Сделай так, чтобы при заходе на любой неизвестный адрес, сервер возвращал файл `spa/build/index.html`. В этом помогут специальные символы [в путях](https://expressjs.com/en/guide/routing.html#route-paths) +--2. Сделай так, чтобы при заходе на любой неизвестный адрес, сервер возвращал файл `spa/build/index.html`. В этом помогут специальные символы [в путях](https://expressjs.com/en/guide/routing.html#route-paths) -3. Сделай так, чтобы наш сайт работал по https. В этом поможет [этот небольшой пост](https://timonweb.com/posts/running-expressjs-server-over-https/). Сертификат уже сгенерирован и лежит в папке `/certs`. +--3. Сделай так, чтобы наш сайт работал по https. В этом поможет [этот небольшой пост](https://timonweb.com/posts/running-expressjs-server-over-https/). Сертификат уже сгенерирован и лежит в папке `/certs`. Обрати внимание, что придётся разрешить Chrome работать с само-подписанными сертификатами для localhost. Это можно сделать включив флаг `chrome://flags/#allow-insecure-localhost`. -4. Изучи файл `client.mjs`. В нём лежит заготовка клиента, который будет делать запросы на сервер. +--4. Изучи файл `client.mjs`. В нём лежит заготовка клиента, который будет делать запросы на сервер. **!!! В этом интенсиве сначала надо реализовать логинизацию. Без этого остальные странички не будут отображаться** @@ -36,19 +36,19 @@ Отправлять ответ можно с помощью [res.json](https://expressjs.com/en/4x/api.html#res.json). -5. Сохрани имя пользователя в [cookie](https://expressjs.com/en/4x/api.html#req.cookies) (не забудь подключить `cookie-parser` [middleware](https://expressjs.com/en/resources/middleware/cookie-parser.html)). +--5. Сохрани имя пользователя в [cookie](https://expressjs.com/en/4x/api.html#req.cookies) (не забудь подключить `cookie-parser` [middleware](https://expressjs.com/en/resources/middleware/cookie-parser.html)). Сделай так, чтобы методы `.getUser()`, `.loginUser()`, `.logoutUser()` работали с cookie -6. Сделай так, чтобы cookie с именем пользователя была `HttpOnly`, `Secure`, и имела `SameSite` политику `Strict`. В этом помогут дополнительные опции [res.cookie](https://expressjs.com/en/4x/api.html#res.cookie). +--6. Сделай так, чтобы cookie с именем пользователя была `HttpOnly`, `Secure`, и имела `SameSite` политику `Strict`. В этом помогут дополнительные опции [res.cookie](https://expressjs.com/en/4x/api.html#res.cookie). -7. Сделай так, чтобы при заходе на любой роут приложения, кроме api, статики и `/login` без cookie происходил редирект на страницу `/login`. +--7. Сделай так, чтобы при заходе на любой роут приложения, кроме api, статики и `/login` без cookie происходил редирект на страницу `/login`. Для этого придётся написать `middleware` и проверять наличие cookie в запросе. Как написать узнай [здесь](https://expressjs.com/en/guide/writing-middleware.html). Сделай так, чтобы middleware применялось только для путей, которые непосредственно отдают `index.html` -8. Оживи остальные страницы кроме `/sendToMars`. А именно `About`, `History`, `Rockets`, `Roadster`. +--8. Оживи остальные страницы кроме `/sendToMars`. А именно `About`, `History`, `Rockets`, `Roadster`. В качестве источника данных используй [публичное API](https://docs.spacexdata.com/). Методы в нём названы похожим образом. diff --git a/client.mjs b/client.mjs index a05a777..dc10ed1 100644 --- a/client.mjs +++ b/client.mjs @@ -6,7 +6,9 @@ export class Client { * @return {Promise} username * */ async getUser() { - throw new Error("Not implemented"); + return fetch('api/user').then( + async res => (await res.json())['username'] + ); } /** @@ -17,7 +19,11 @@ export class Client { * @return {Promise} username * */ async loginUser(username) { - throw new Error("Not implemented"); + return fetch(`api/login?username=${username}`).then(async res => { + let json = await res.json(); + let username = json['username']; + return username; + }); } /** @@ -26,7 +32,7 @@ export class Client { * @return {void} * */ async logoutUser() { - throw new Error("Not implemented"); + await fetch('api/logout'); } /** @@ -50,7 +56,7 @@ export class Client { * @return {Promise} * */ async getInfo() { - throw new Error("Not implemented"); + return (await fetch("https://api.spacexdata.com/v3/info")).json(); } /** @@ -63,7 +69,7 @@ export class Client { * @return {Promise} * */ async getHistory() { - throw new Error("Not implemented"); + return (await fetch('https://api.spacexdata.com/v3/history')).json(); } /** @@ -80,7 +86,7 @@ export class Client { * @return {Promise} * */ async getHistoryEvent(id) { - throw new Error("Not implemented"); + return (await fetch(`https://api.spacexdata.com/v3/history/${id}`)).json(); } /** @@ -93,7 +99,7 @@ export class Client { * @return {Promise} * */ async getRockets() { - throw new Error("Not implemented"); + return (await fetch('https://api.spacexdata.com/v3/rockets')).json(); } /** @@ -118,7 +124,7 @@ export class Client { * @return {Promise} * */ async getRocket(id) { - throw new Error("Not implemented"); + return (await fetch(`https://api.spacexdata.com/v3/rockets/${id}`)).json(); } /** @@ -135,7 +141,7 @@ export class Client { * @return {Promise} * */ async getRoadster() { - throw new Error("Not implemented"); + return (await fetch('https://api.spacexdata.com/v3/roadster')).json(); } /** diff --git a/package-lock.json b/package-lock.json index 87f8931..4ae7fc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,52 +31,17 @@ } }, "ansi-align": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.0.tgz", - "integrity": "sha512-ZpClVKqXN3RGBmKibdfWzqCY4lnjEuoNzU5T0oEFpfd/z5qJHVarukridD4juLO2FXMiwUQxr9WqQtaYa8XRYw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", "requires": { - "string-width": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", - "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" - }, - "emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "requires": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - } - }, - "strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "requires": { - "ansi-regex": "^4.1.0" - } - } + "string-width": "^4.1.0" } }, "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" }, "ansi-styles": { "version": "4.3.0", @@ -517,9 +482,9 @@ } }, "glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", "requires": { "is-glob": "^4.0.1" } @@ -611,9 +576,9 @@ "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "ipaddr.js": { "version": "1.9.1", @@ -846,9 +811,9 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "normalize-url": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.0.tgz", - "integrity": "sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==" + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz", + "integrity": "sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==" }, "object-assign": { "version": "4.1.1", diff --git a/server.mjs b/server.mjs index 75d9cbe..1401779 100644 --- a/server.mjs +++ b/server.mjs @@ -10,6 +10,28 @@ const rootDir = process.cwd(); const port = 3000; const app = express(); + + +const loginMiddleware = (req, res, next) => { + const dontRedirect = ['api', 'static', 'login', 'client.mjs']; + + let route = req.url.split("/")[1]; + + if (!req.cookies.username && !dontRedirect.includes(route)) { + res.redirect("/login"); + } else { + next(); + } +}; + +app.use(express.static('spa/build')); +app.use(cookieParser()); +app.use(loginMiddleware); + +app.get('/login', (req, res) => { + res.sendFile(path.join(rootDir, "spa/build/index.html")); +}); + app.get("/client.mjs", (_, res) => { res.header("Cache-Control", "private, no-cache, no-store, must-revalidate"); res.sendFile(path.join(rootDir, "client.mjs"), { @@ -18,10 +40,36 @@ app.get("/client.mjs", (_, res) => { }); }); -app.get("/", (_, res) => { - res.send(":)"); +app.get("/api/login", (req, res) => { + let username = req.query.username; + res.cookie("username", username, { + httpOnly: true, + secure: true, + sameSite: 'strict' + }); + res.json({'username': username}); +}); + +app.get("/api/user", (req, res) => { + let username = req.cookies.username; + res.json({'username': username}); +}); + +app.get("/api/logout", (req, res) => { + res.clearCookie("username"); + res.redirect("/"); +}); + +app.get('/*', (req, res) => { + res.redirect("/"); }); -app.listen(port, () => { +https.createServer( + { + key: fs.readFileSync("certs/server.key"), + cert: fs.readFileSync("certs/server.cert"), + }, + app +).listen(port, function () { console.log(`App listening on port ${port}`); });