diff --git a/Justfile b/Justfile index 24966fa..b71e040 100644 --- a/Justfile +++ b/Justfile @@ -4,6 +4,7 @@ # For launching the authentification api @auth $FASTIFY_LOG_LEVEL="info" $FASTIFY_PRETTY_LOGS="true": fastify start src/api/auth/default.js + # For launching the user data api @user $FASTIFY_LOG_LEVEL="info" $FASTIFY_PRETTY_LOGS="true": fastify start src/api/user/default.js diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 15819d1..98d3b4d 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,3 +1,5 @@ +name: ft_transcendence + services: front: container_name: transcendence-front @@ -5,7 +7,7 @@ services: dockerfile: docker/front/Dockerfile context: .. ports: - - 8443:443 + - ${OUT_PORT}:443 environment: SERVER_NAME: localhost depends_on: @@ -15,35 +17,48 @@ services: condition: service_started networks: - front + environment: + - TZ=Europe/Paris + restart: unless-stopped user-api: container_name: transcendence-api-user build: dockerfile: docker/api-base/Dockerfile context: .. - tags: - - api-base + volumes: + - db-user:/db networks: - front - back environment: + - TZ=Europe/Paris - API_TARGET=user + - JWT_SECRET=${JWT_SECRET} + restart: unless-stopped auth-api: container_name: transcendence-api-auth build: dockerfile: docker/api-base/Dockerfile context: .. + volumes: + - db-auth:/db networks: - front - back environment: + - TZ=Europe/Paris - API_TARGET=auth - - + - JWT_SECRET=${JWT_SECRET} + restart: unless-stopped networks: front: - external: false - name: front-backend + name: transcendence-front back: - external: false - name: trans-backend + name: transcendence-back + +volumes: + db-auth: + name: transcendence-api-auth-db + db-user: + name: transcendence-api-user-db diff --git a/package.json b/package.json index 839ae51..242fcb8 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,15 @@ "bcrypt": "^6.0.0", "better-sqlite3": "^12.2.0", "fastify": "^5.4.0", - "fastify-cli": "^7.4.0" + "fastify-cli": "^7.4.0", + "google-auth-library": "^10.1.0" }, "type": "module", "devDependencies": { - "typescript": "^5.8.3", - "tailwindcss": "^4.1.11", "@tailwindcss/vite": "^4.1.11", "pino-pretty": "^13.0.0", + "tailwindcss": "^4.1.11", + "typescript": "^5.8.3", "vite": "^6.3.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4a38815..99e43ba 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -29,6 +29,9 @@ importers: fastify-cli: specifier: ^7.4.0 version: 7.4.0 + google-auth-library: + specifier: ^10.1.0 + version: 10.1.0 devDependencies: '@tailwindcss/vite': specifier: ^4.1.11 @@ -455,6 +458,10 @@ packages: abstract-logging@2.0.1: resolution: {integrity: sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==} + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + ajv-formats@3.0.1: resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==} peerDependencies: @@ -491,6 +498,9 @@ packages: resolution: {integrity: sha512-eGbYq2CT+tos1fBwLQ/tkBt9J5M3JEHjku4hbvQUePCckkvVf14xWj+1m7dGoK81M/fOjFT7yM9UMeKT/+vFLQ==} engines: {node: 20.x || 22.x || 23.x || 24.x} + bignumber.js@9.3.1: + resolution: {integrity: sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==} + bindings@1.5.0: resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==} @@ -500,6 +510,9 @@ packages: bn.js@4.12.2: resolution: {integrity: sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer@5.7.1: resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} @@ -538,9 +551,22 @@ packages: resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} engines: {node: '>=18'} + data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dateformat@4.6.3: resolution: {integrity: sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==} + debug@4.4.1: + resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + decompress-response@6.0.0: resolution: {integrity: sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==} engines: {node: '>=10'} @@ -587,6 +613,9 @@ packages: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} engines: {node: '>=6'} + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + fast-copy@3.0.2: resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} @@ -647,6 +676,10 @@ packages: picomatch: optional: true + fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + file-uri-to-path@1.0.0: resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==} @@ -658,6 +691,10 @@ packages: resolution: {integrity: sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==} engines: {node: '>=6'} + formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -666,6 +703,14 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] + gaxios@7.1.1: + resolution: {integrity: sha512-Odju3uBUJyVCkW64nLD4wKLhbh93bh6vIg/ZIXkWiLPBrdgtc65+tls/qml+un3pr6JqYVFDZbbmLDQT68rTOQ==} + engines: {node: '>=18'} + + gcp-metadata@7.0.1: + resolution: {integrity: sha512-UcO3kefx6dCcZkgcTGgVOTFb7b1LlQ02hY1omMjjrrBzkajRMCFgYOjs7J71WqnuG1k2b+9ppGL7FsOfhZMQKQ==} + engines: {node: '>=18'} + generify@4.2.0: resolution: {integrity: sha512-b4cVhbPfbgbCZtK0dcUc1lASitXGEAIqukV5DDAyWm25fomWnV+C+a1yXvqikcRZXHN2j0pSDyj3cTfzq8pC7Q==} hasBin: true @@ -673,9 +718,21 @@ packages: github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} + google-auth-library@10.1.0: + resolution: {integrity: sha512-GspVjZj1RbyRWpQ9FbAXMKjFGzZwDKnUHi66JJ+tcjcu5/xYAP1pdlWotCuIkMwjfVsxxDvsGZXGLzRt72D0sQ==} + engines: {node: '>=18'} + + google-logging-utils@1.1.1: + resolution: {integrity: sha512-rcX58I7nqpu4mbKztFeOAObbomBbHU2oIb/d3tJfF3dizGSApqtSwYJigGCooHdnMyQBIw8BrWyK96w3YXgr6A==} + engines: {node: '>=14'} + graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + gtoken@8.0.0: + resolution: {integrity: sha512-+CqsMbHPiSTdtSO14O51eMNlrp9N79gmeqmXeouJOhfucAedHw9noVe/n5uJk3tbKE6a+6ZCQg3RPhVhHByAIw==} + engines: {node: '>=18'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -683,6 +740,10 @@ packages: help-me@5.0.0: resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==} + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + ieee754@1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} @@ -713,12 +774,21 @@ packages: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} + json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + json-schema-ref-resolver@2.0.1: resolution: {integrity: sha512-HG0SIB9X4J8bwbxCbnd5FfPEbcXAJYTi1pBJeP/QPON+w8ovSME8iRG+ElHNxZNX2Qh6eYn1GdzJFS4cDFfx0Q==} json-schema-traverse@1.0.0: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.0: + resolution: {integrity: sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==} + light-my-request@6.6.0: resolution: {integrity: sha512-CHYbu8RtboSIoVsHZ6Ye4cj4Aw/yg2oAFimlF7mNvfDV192LR7nDiKtSIfCuLT7KokPSTn/9kfVLm5OGN0A28A==} @@ -825,6 +895,9 @@ packages: mnemonist@0.40.3: resolution: {integrity: sha512-Vjyr90sJ23CKKH/qPAgUKicw/v6pRoamxIEDFOF8uSgFME7DqPRpHgRTejWVjkdGg5dXj0/NyxZHZ9bcjH+2uQ==} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.11: resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -841,6 +914,15 @@ packages: resolution: {integrity: sha512-D9DI/gXHvVmjHS08SVch0Em8G5S1P+QWtU31appcKT/8wFSPRcdHadIFSAntdMMVM5zz+/DL+bL/gz3UDppqtg==} engines: {node: ^18 || ^20 || >= 21} + node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead + + node-fetch@3.3.2: + resolution: {integrity: sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true @@ -1108,6 +1190,10 @@ packages: walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -1406,6 +1492,8 @@ snapshots: abstract-logging@2.0.1: {} + agent-base@7.1.4: {} + ajv-formats@3.0.1(ajv@8.17.1): optionalDependencies: ajv: 8.17.1 @@ -1447,6 +1535,8 @@ snapshots: bindings: 1.5.0 prebuild-install: 7.1.3 + bignumber.js@9.3.1: {} + bindings@1.5.0: dependencies: file-uri-to-path: 1.0.0 @@ -1459,6 +1549,8 @@ snapshots: bn.js@4.12.2: {} + buffer-equal-constant-time@1.0.1: {} + buffer@5.7.1: dependencies: base64-js: 1.5.1 @@ -1491,8 +1583,14 @@ snapshots: cookie@1.0.2: {} + data-uri-to-buffer@4.0.1: {} + dateformat@4.6.3: {} + debug@4.4.1: + dependencies: + ms: 2.1.3 + decompress-response@6.0.0: dependencies: mimic-response: 3.1.0 @@ -1557,6 +1655,8 @@ snapshots: expand-template@2.0.3: {} + extend@3.0.2: {} + fast-copy@3.0.2: {} fast-decode-uri-component@1.0.1: {} @@ -1650,6 +1750,11 @@ snapshots: optionalDependencies: picomatch: 4.0.2 + fetch-blob@3.2.0: + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + file-uri-to-path@1.0.0: {} find-my-way@9.3.0: @@ -1662,11 +1767,31 @@ snapshots: dependencies: locate-path: 3.0.0 + formdata-polyfill@4.0.10: + dependencies: + fetch-blob: 3.2.0 + fs-constants@1.0.0: {} fsevents@2.3.3: optional: true + gaxios@7.1.1: + dependencies: + extend: 3.0.2 + https-proxy-agent: 7.0.6 + node-fetch: 3.3.2 + transitivePeerDependencies: + - supports-color + + gcp-metadata@7.0.1: + dependencies: + gaxios: 7.1.1 + google-logging-utils: 1.1.1 + json-bigint: 1.0.0 + transitivePeerDependencies: + - supports-color + generify@4.2.0: dependencies: isbinaryfile: 4.0.10 @@ -1676,12 +1801,40 @@ snapshots: github-from-package@0.0.0: {} + google-auth-library@10.1.0: + dependencies: + base64-js: 1.5.1 + ecdsa-sig-formatter: 1.0.11 + gaxios: 7.1.1 + gcp-metadata: 7.0.1 + google-logging-utils: 1.1.1 + gtoken: 8.0.0 + jws: 4.0.0 + transitivePeerDependencies: + - supports-color + + google-logging-utils@1.1.1: {} + graceful-fs@4.2.11: {} + gtoken@8.0.0: + dependencies: + gaxios: 7.1.1 + jws: 4.0.0 + transitivePeerDependencies: + - supports-color + has-flag@4.0.0: {} help-me@5.0.0: {} + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.1 + transitivePeerDependencies: + - supports-color + ieee754@1.2.1: {} inherits@2.0.4: {} @@ -1698,12 +1851,27 @@ snapshots: joycon@3.1.1: {} + json-bigint@1.0.0: + dependencies: + bignumber.js: 9.3.1 + json-schema-ref-resolver@2.0.1: dependencies: dequal: 2.0.3 json-schema-traverse@1.0.0: {} + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.0: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + light-my-request@6.6.0: dependencies: cookie: 1.0.2 @@ -1788,6 +1956,8 @@ snapshots: dependencies: obliterator: 2.0.5 + ms@2.1.3: {} + nanoid@3.3.11: {} napi-build-utils@2.0.0: {} @@ -1798,6 +1968,14 @@ snapshots: node-addon-api@8.4.0: {} + node-domexception@1.0.0: {} + + node-fetch@3.3.2: + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + node-gyp-build@4.8.4: {} obliterator@2.0.5: {} @@ -2073,6 +2251,8 @@ snapshots: dependencies: makeerror: 1.0.12 + web-streams-polyfill@3.3.3: {} + wrappy@1.0.2: {} xtend@4.0.2: {} diff --git a/src/api/auth/default.js b/src/api/auth/default.js index 4588edd..042245d 100644 --- a/src/api/auth/default.js +++ b/src/api/auth/default.js @@ -1,55 +1,13 @@ import fastifyJWT from '@fastify/jwt'; import fastifyCookie from '@fastify/cookie'; -import Database from 'better-sqlite3'; -import bcrypt from 'bcrypt'; -const RESERVED_USERNAMES = ['admin']; -var env = process.env.NODE_ENV || 'development'; +import { register } from './register.js'; +import { login } from './login.js'; +import authDB from '../../utils/authDB.js' const saltRounds = 10; -let database; -if (env === 'development') { - database = new Database(":memory:", { verbose: console.log }); -} else { - var dbPath = process.env.DB_PATH || '/db/db.sqlite' - database = new Database(dbPath); -} - -/** - * @description Can be used to prepare the database - */ -function prepareDB() { - database.exec(` - CREATE TABLE credentials ( - username TEXT PRIMARY KEY, - passwordHash TEXT - ) STRICT - `); -} - -prepareDB() - -const userCheck = database.prepare('SELECT EXISTS (SELECT 1 FROM credentials WHERE username = ?);'); -const passwordQuery = database.prepare('SELECT passwordHash FROM credentials WHERE username = ?;'); -const userAdd = database.prepare('INSERT INTO credentials (username, passwordHash) VALUES (?, ?)'); - -/** - * @description Can be used to check is a user exists in the database - * @param {string} name - * - * @returns {boolean} - */ -function checkUser(name) { - const result = userCheck.get(name); - const key = Object.keys(result)[0]; - - return result[key] === 1; -} - -function isValidString(value) { - return typeof value === 'string' && value.trim() !== ''; -} +authDB.prepareDB(); /** * @param {import('fastify').FastifyInstance} fastify @@ -67,6 +25,19 @@ export default async function(fastify, options) { }); fastify.register(fastifyCookie); + fastify.get('/me', async (request, reply) => { + try { + const token = request.cookies.token; + const decoded = await fastify.jwt.verify(token); + return { user: decoded.user }; + } catch { + return reply.code(401).send({ error: 'Unauthorized' }); + } + }); + + // GOOGLE sign in + + fastify.post('/login', { schema: { body: { @@ -78,44 +49,7 @@ export default async function(fastify, options) { } } } - }, async (request, reply) => { - try { - /** @type {{ user: string, password: string }} */ - const { user, password } = request.body; - - if (!checkUser(user) || user === 'admin') { - return reply.code(400).send({ error: "User does not exist" }); - } - - const query = passwordQuery.get(user); - const hash = query?.passwordHash; - - if (!hash) { - return reply.code(500).send({ error: "No password was found" }); - } - - const compare = await bcrypt.compare(password, hash); - - if (!compare) { - return reply.code(401).send({ error: "Incorrect password" }); - } - - const token = fastify.jwt.sign({ user }); - - return reply - .setCookie('token', token, { - httpOnly: true, - path: '/', - secure: env !== 'development', - sameSite: 'lax', - }) - .code(200) - .send({ msg: "Login successful" }); - } catch (err) { - fastify.log.error(err); - return reply.code(500).send({ error: "Internal server error" }); - } - }); + }, async (request, reply) => { return login(request, reply, fastify); }); fastify.post('/register', { schema: { @@ -128,41 +62,5 @@ export default async function(fastify, options) { } } } - }, async (request, reply) => { - try { - /** @type {{ user: string, password: string }} */ - const { user, password } = request.body; - - if (RESERVED_USERNAMES.includes(user)) { - return reply.code(400).send({ error: 'Reserved username' }); - } - - if (!isValidString(user) || !isValidString(password)) { - return reply.code(400).send({ error: 'Invalid username or password' }); - } else if (checkUser(user) === true) { - return reply.code(400).send({ error: "User already exist" }); - } else if (password.length <= 8) { - return reply.code(400).send({ error: "Password too short" }); - } else if (password.length > 64) { - return reply.code(400).send({ error: "Password too long" }); - } - - const hash = await bcrypt.hash(password, saltRounds); - userAdd.run(user, hash); - return reply.code(200).send({ msg: 'Register successfuly' }); - } catch (err) { - fastify.log.error(err); - return reply.code(500).send({ error: "Internal server error" }); - } - }); - - fastify.get('/me', async (request, reply) => { - try { - const token = request.cookies.token; - const decoded = await fastify.jwt.verify(token); - return { user: decoded.user }; - } catch { - return reply.code(401).send({ error: 'Unauthorized' }); - } - }); + }, async (request, reply) => { return register(request, reply, saltRounds, fastify); }); } diff --git a/src/api/auth/login.js b/src/api/auth/login.js new file mode 100644 index 0000000..133dfee --- /dev/null +++ b/src/api/auth/login.js @@ -0,0 +1,53 @@ +import bcrypt from 'bcrypt'; + +import { checkUser } from '../../utils/authUtils.js'; +import authDB from '../../utils/authDB.js'; + +var env = process.env.NODE_ENV || 'development'; + +/** + * @async + * @param {import("fastify").FastifyRequest} request + * @param {import("fastify").FastifyReply} reply + * @param {import("fastify").FastifyInstance} fastify + * + * @returns {import('fastify').FastifyReply} + */ +export async function login(request, reply, fastify) { + try { + /** @type {{ user: string, password: string }} */ + const { user, password } = request.body; + + if (!checkUser(user) || user === 'admin') { + return reply.code(400).send({ error: "User does not exist" }); + } + + const query = authDB.passwordQuery.get(user); + const hash = query?.passwordHash; + + if (!hash) { + return reply.code(500).send({ error: "No password was found" }); + } + + const compare = await bcrypt.compare(password, hash); + + if (!compare) { + return reply.code(401).send({ error: "Incorrect password" }); + } + + const token = fastify.jwt.sign({ user }); + + return reply + .setCookie('token', token, { + httpOnly: true, + path: '/', + secure: env !== 'development', + sameSite: 'lax', + }) + .code(200) + .send({ msg: "Login successful" }); + } catch (err) { + fastify.log.error(err); + return reply.code(500).send({ error: "Internal server error" }); + } +} diff --git a/src/api/auth/register.js b/src/api/auth/register.js new file mode 100644 index 0000000..a75013a --- /dev/null +++ b/src/api/auth/register.js @@ -0,0 +1,54 @@ +import bcrypt from 'bcrypt'; + +import { isValidString, checkUser } from '../../utils/authUtils.js'; +import authDB from '../../utils/authDB.js'; + +var env = process.env.NODE_ENV || 'development'; + +/** + * @async + * @param {import("fastify").FastifyRequest} request + * @param {import("fastify").FastifyReply} reply + * @param {number} saltRounds + * @param {import("fastify").FastifyInstance} fastify + * + * @returns {import('fastify').FastifyReply} + */ +export async function register(request, reply, saltRounds, fastify) { + try { + /** @type {{ user: string, password: string }} */ + const { user, password } = request.body; + + if (authDB.RESERVED_USERNAMES.includes(user)) { + return reply.code(400).send({ error: 'Reserved username' }); + } + + if (!isValidString(user) || !isValidString(password)) { + return reply.code(400).send({ error: 'Invalid username or password' }); + } else if (checkUser(user) === true) { + return reply.code(400).send({ error: "User already exist" }); + } else if (password.length <= 8) { + return reply.code(400).send({ error: "Password too short" }); + } else if (password.length > 64) { + return reply.code(400).send({ error: "Password too long" }); + } + + const hash = await bcrypt.hash(password, saltRounds); + authDB.userAdd.run(user, hash); + + const token = fastify.jwt.sign({ user }); + + return reply + .setCookie('token', token, { + httpOnly: true, + path: '/', + secure: env !== 'development', + sameSite: 'lax', + }) + .code(200) + .send({ msg: 'Register successfuly' }); + } catch (err) { + fastify.log.error(err); + return reply.code(500).send({ error: "Internal server error" }); + } +} diff --git a/src/utils/authDB.js b/src/utils/authDB.js new file mode 100644 index 0000000..40d84f5 --- /dev/null +++ b/src/utils/authDB.js @@ -0,0 +1,39 @@ +import Database from 'better-sqlite3'; + +var env = process.env.NODE_ENV || 'development'; +let database; +const RESERVED_USERNAMES = ['admin']; +let userCheck, passwordQuery, userAdd; + +if (!env || env === 'development') { + database = new Database(":memory:", { verbose: console.log }); +} else { + var dbPath = process.env.DB_PATH || '/db/db.sqlite'; + database = new Database(dbPath); +} + +/** + * @description Can be used to prepare the database + */ +function prepareDB() { + database.exec(` + CREATE TABLE IF NOT EXISTS credentials ( + username TEXT PRIMARY KEY, + passwordHash TEXT + ) STRICT + `); + userCheck = database.prepare('SELECT EXISTS (SELECT 1 FROM credentials WHERE username = ?);'); + passwordQuery = database.prepare('SELECT passwordHash FROM credentials WHERE username = ?;'); + userAdd = database.prepare('INSERT INTO credentials (username, passwordHash) VALUES (?, ?)'); +} + + +const authDB = { + prepareDB, + get userCheck() { return userCheck; }, + get userAdd() { return userAdd; }, + get passwordQuery() { return passwordQuery; }, + RESERVED_USERNAMES +}; + +export default authDB; diff --git a/src/utils/authUtils.js b/src/utils/authUtils.js new file mode 100644 index 0000000..d5062a1 --- /dev/null +++ b/src/utils/authUtils.js @@ -0,0 +1,23 @@ +import authDB from './authDB.js'; + +/** + * @param {string} value + * + * @returns {boolean} + */ +export function isValidString(value) { + return typeof value === 'string' && value.trim() !== ''; +} + +/** + * @param {string} name + * @param {import('better-sqlite3').Statement} userCheck + * + * @returns {boolean} + */ +export function checkUser(name, userCheck) { + const result = authDB.userCheck.get(name); + const key = Object.keys(result)[0]; + + return result[key] === 1; +}