From: Adam Dullage Date: Thu, 19 Aug 2021 07:26:15 +0000 (+0100) Subject: Added Authentication X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=f12532196e57a01a0e308f6fa946df9271ffa534;p=flatnotes.git Added Authentication --- diff --git a/Pipfile b/Pipfile index 38cd0f6..c6c9639 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,7 @@ whoosh = "*" fastapi = "*" uvicorn = {extras = ["standard"], version = "*"} aiofiles = "*" +python-jose = {extras = ["cryptography"], version = "*"} [requires] python_version = "3.8" diff --git a/Pipfile.lock b/Pipfile.lock index 690fdf2..d7d1220 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "7bdbb656d72c9dd9bed01397a477e2f03880651d32fcbcae6aa0b5992f6c9da3" + "sha256": "f03f2187cd2a303a19231321028d98b5a755f2a20ff2601ce7b4739cfe9d5ae4" }, "pipfile-spec": 6, "requires": { @@ -32,6 +32,56 @@ "markers": "python_version >= '3.6'", "version": "==3.4.1" }, + "cffi": { + "hashes": [ + "sha256:06c54a68935738d206570b20da5ef2b6b6d92b38ef3ec45c5422c0ebaf338d4d", + "sha256:0c0591bee64e438883b0c92a7bed78f6290d40bf02e54c5bf0978eaf36061771", + "sha256:19ca0dbdeda3b2615421d54bef8985f72af6e0c47082a8d26122adac81a95872", + "sha256:22b9c3c320171c108e903d61a3723b51e37aaa8c81255b5e7ce102775bd01e2c", + "sha256:26bb2549b72708c833f5abe62b756176022a7b9a7f689b571e74c8478ead51dc", + "sha256:33791e8a2dc2953f28b8d8d300dde42dd929ac28f974c4b4c6272cb2955cb762", + "sha256:3c8d896becff2fa653dc4438b54a5a25a971d1f4110b32bd3068db3722c80202", + "sha256:4373612d59c404baeb7cbd788a18b2b2a8331abcc84c3ba40051fcd18b17a4d5", + "sha256:487d63e1454627c8e47dd230025780e91869cfba4c753a74fda196a1f6ad6548", + "sha256:48916e459c54c4a70e52745639f1db524542140433599e13911b2f329834276a", + "sha256:4922cd707b25e623b902c86188aca466d3620892db76c0bdd7b99a3d5e61d35f", + "sha256:55af55e32ae468e9946f741a5d51f9896da6b9bf0bbdd326843fec05c730eb20", + "sha256:57e555a9feb4a8460415f1aac331a2dc833b1115284f7ded7278b54afc5bd218", + "sha256:5d4b68e216fc65e9fe4f524c177b54964af043dde734807586cf5435af84045c", + "sha256:64fda793737bc4037521d4899be780534b9aea552eb673b9833b01f945904c2e", + "sha256:6d6169cb3c6c2ad50db5b868db6491a790300ade1ed5d1da29289d73bbe40b56", + "sha256:7bcac9a2b4fdbed2c16fa5681356d7121ecabf041f18d97ed5b8e0dd38a80224", + "sha256:80b06212075346b5546b0417b9f2bf467fea3bfe7352f781ffc05a8ab24ba14a", + "sha256:818014c754cd3dba7229c0f5884396264d51ffb87ec86e927ef0be140bfdb0d2", + "sha256:8eb687582ed7cd8c4bdbff3df6c0da443eb89c3c72e6e5dcdd9c81729712791a", + "sha256:99f27fefe34c37ba9875f224a8f36e31d744d8083e00f520f133cab79ad5e819", + "sha256:9f3e33c28cd39d1b655ed1ba7247133b6f7fc16fa16887b120c0c670e35ce346", + "sha256:a8661b2ce9694ca01c529bfa204dbb144b275a31685a075ce123f12331be790b", + "sha256:a9da7010cec5a12193d1af9872a00888f396aba3dc79186604a09ea3ee7c029e", + "sha256:aedb15f0a5a5949ecb129a82b72b19df97bbbca024081ed2ef88bd5c0a610534", + "sha256:b315d709717a99f4b27b59b021e6207c64620790ca3e0bde636a6c7f14618abb", + "sha256:ba6f2b3f452e150945d58f4badd92310449876c4c954836cfb1803bdd7b422f0", + "sha256:c33d18eb6e6bc36f09d793c0dc58b0211fccc6ae5149b808da4a62660678b156", + "sha256:c9a875ce9d7fe32887784274dd533c57909b7b1dcadcc128a2ac21331a9765dd", + "sha256:c9e005e9bd57bc987764c32a1bee4364c44fdc11a3cc20a40b93b444984f2b87", + "sha256:d2ad4d668a5c0645d281dcd17aff2be3212bc109b33814bbb15c4939f44181cc", + "sha256:d950695ae4381ecd856bcaf2b1e866720e4ab9a1498cba61c602e56630ca7195", + "sha256:e22dcb48709fc51a7b58a927391b23ab37eb3737a98ac4338e2448bef8559b33", + "sha256:e8c6a99be100371dbb046880e7a282152aa5d6127ae01783e37662ef73850d8f", + "sha256:e9dc245e3ac69c92ee4c167fbdd7428ec1956d4e754223124991ef29eb57a09d", + "sha256:eb687a11f0a7a1839719edd80f41e459cc5366857ecbed383ff376c4e3cc6afd", + "sha256:eb9e2a346c5238a30a746893f23a9535e700f8192a68c07c0258e7ece6ff3728", + "sha256:ed38b924ce794e505647f7c331b22a693bee1538fdf46b0222c4717b42f744e7", + "sha256:f0010c6f9d1a4011e429109fda55a225921e3206e7f62a0c22a35344bfd13cca", + "sha256:f0c5d1acbfca6ebdd6b1e3eded8d261affb6ddcf2186205518f1428b8569bb99", + "sha256:f10afb1004f102c7868ebfe91c28f4a712227fe4cb24974350ace1f90e1febbf", + "sha256:f174135f5609428cc6e1b9090f9268f5c8935fddb1b25ccb8255a2d50de6789e", + "sha256:f3ebe6e73c319340830a9b2825d32eb6d8475c1dac020b4f0aa774ee3b898d1c", + "sha256:f627688813d0a4140153ff532537fbe4afea5a3dffce1f9deb7f91f848a832b5", + "sha256:fd4305f86f53dfd8cd3522269ed7fc34856a8ee3709a5e28b2836b2db9d4cd69" + ], + "version": "==1.14.6" + }, "click": { "hashes": [ "sha256:8c04c11192119b1ef78ea049e0a6f0463e4c48ef00a30160c704337586f3ad7a", @@ -47,6 +97,33 @@ ], "version": "==0.4.4" }, + "cryptography": { + "hashes": [ + "sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d", + "sha256:1e056c28420c072c5e3cb36e2b23ee55e260cb04eee08f702e0edfec3fb51959", + "sha256:240f5c21aef0b73f40bb9f78d2caff73186700bf1bc6b94285699aff98cc16c6", + "sha256:26965837447f9c82f1855e0bc8bc4fb910240b6e0d16a664bb722df3b5b06873", + "sha256:37340614f8a5d2fb9aeea67fd159bfe4f5f4ed535b1090ce8ec428b2f15a11f2", + "sha256:3d10de8116d25649631977cb37da6cbdd2d6fa0e0281d014a5b7d337255ca713", + "sha256:3d8427734c781ea5f1b41d6589c293089704d4759e34597dce91014ac125aad1", + "sha256:7ec5d3b029f5fa2b179325908b9cd93db28ab7b85bb6c1db56b10e0b54235177", + "sha256:8e56e16617872b0957d1c9742a3f94b43533447fd78321514abbe7db216aa250", + "sha256:b01fd6f2737816cb1e08ed4807ae194404790eac7ad030b34f2ce72b332f5586", + "sha256:bf40af59ca2465b24e54f671b2de2c59257ddc4f7e5706dbd6930e26823668d3", + "sha256:de4e5f7f68220d92b7637fc99847475b59154b7a1b3868fb7385337af54ac9ca", + "sha256:eb8cc2afe8b05acbd84a43905832ec78e7b3873fb124ca190f574dca7389a87d", + "sha256:ee77aa129f481be46f8d92a1a7db57269a2f23052d5f2433b4621bb457081cc9" + ], + "version": "==3.4.7" + }, + "ecdsa": { + "hashes": [ + "sha256:5cf31d5b33743abe0dfc28999036c849a69d548f994b535e527ee3cb7f3ef676", + "sha256:b9f500bb439e4153d0330610f5d26baaf18d17b8ced1bc54410d189385ea68aa" + ], + "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==0.17.0" + }, "fastapi": { "hashes": [ "sha256:c9256a89b0436223b45f53fe3a39b178f3da6be5841a2c59deedff4b676d003f", @@ -83,6 +160,32 @@ ], "version": "==0.2.0" }, + "pyasn1": { + "hashes": [ + "sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359", + "sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576", + "sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf", + "sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7", + "sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d", + "sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00", + "sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8", + "sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86", + "sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12", + "sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776", + "sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba", + "sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2", + "sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3" + ], + "version": "==0.4.8" + }, + "pycparser": { + "hashes": [ + "sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0", + "sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==2.20" + }, "pydantic": { "hashes": [ "sha256:021ea0e4133e8c824775a0cfe098677acf6fa5a3cbf9206a376eed3fc09302cd", @@ -118,6 +221,17 @@ ], "version": "==0.19.0" }, + "python-jose": { + "extras": [ + "cryptography" + ], + "hashes": [ + "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a", + "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a" + ], + "index": "pypi", + "version": "==3.3.0" + }, "pyyaml": { "hashes": [ "sha256:08682f6b72c722394747bddaf0aa62277e02557c0fd1c42cb853016a38f8dedf", @@ -152,6 +266,22 @@ ], "version": "==5.4.1" }, + "rsa": { + "hashes": [ + "sha256:78f9a9bf4e7be0c5ded4583326e7461e3a3c5aae24073648b4bdfa797d78c9d2", + "sha256:9d689e6ca1b3038bc82bf8d23e944b6b6037bc02301a574935b2dd946e0353b9" + ], + "markers": "python_version >= '3.5' and python_version < '4'", + "version": "==4.7.2" + }, + "six": { + "hashes": [ + "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", + "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" + ], + "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", + "version": "==1.16.0" + }, "starlette": { "hashes": [ "sha256:3c8e48e52736b3161e34c9f0e8153b4f32ec5d8995a3ee1d59410d92f75162ed", @@ -173,11 +303,11 @@ "standard" ], "hashes": [ - "sha256:2a76bb359171a504b3d1c853409af3adbfa5cef374a4a59e5881945a97a93eae", - "sha256:45ad7dfaaa7d55cab4cd1e85e03f27e9d60bc067ddc59db52a2b0aeca8870292" + "sha256:17f898c64c71a2640514d4089da2689e5db1ce5d4086c2d53699bf99513421c1", + "sha256:d9a3c0dd1ca86728d3e235182683b4cf94cd53a867c288eaeca80ee781b2caff" ], "index": "pypi", - "version": "==0.14.0" + "version": "==0.15.0" }, "watchgod": { "hashes": [ @@ -365,11 +495,11 @@ }, "tomli": { "hashes": [ - "sha256:056f0376bf5a6b182c513f9582c1e5b0487265eb6c48842b69aa9ca1cd5f640a", - "sha256:d60e681734099207a6add7a10326bc2ddd1fdc36c1b0f547d00ef73ac63739c2" + "sha256:8dd0e9524d6f386271a36b41dbf6c57d8e32fd96fd22b6584679dc569d20899f", + "sha256:a5b75cb6f3968abb47af1b40c1819dc519ea82bcc065776a866e8d74c5ca9442" ], "markers": "python_version >= '3.6'", - "version": "==1.2.0" + "version": "==1.2.1" } } } diff --git a/README.md b/README.md index 6335e1a..435d578 100644 --- a/README.md +++ b/README.md @@ -29,16 +29,16 @@ This is what flatnotes aims to achieve. * [x] Proof of Concept - Stage 1 * [x] Notes List * [x] Full Text Searching -* [ ] Proof of Concept - Stage 2 +* [x] Proof of Concept - Stage 2 * [x] View Note Content * [x] Edit Note Content * [x] Docker Deployment - * [ ] Password Authentication + * [x] Password Authentication * [ ] Proof of Concept - Stage 3 + * [ ] Public URL Sharing +* [ ] Proof of Concept - Stage 4 * [ ] Image Embedding * [ ] Attachment Upload -* [ ] Proof of Concept - Stage 4 - * [ ] Public URL Sharing * [ ] First Release * [ ] Clean & Responsive UI * [ ] Ability to Create a Note diff --git a/docker-compose.yaml b/docker-compose.yaml index 0fb62be..5697015 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,9 +5,14 @@ services: container_name: flatnotes image: dullage/flatnotes:latest build: . + environment: + FLATNOTES_USERNAME: "user" + FLATNOTES_PASSWORD: "changeMe!" + FLATNOTES_SECRET_KEY: "aLongRandomSeriesOfCharacters" + # FLATNOTES_SESSION_EXPIRY_DAYS: "7" # Optional. Defaults to 30. volumes: - "./data:/data" - # - "./index:/data/.flatnotes" # Optional + # - "./index:/data/.flatnotes" # Optional. ports: - "80:80" restart: unless-stopped diff --git a/flatnotes/auth.py b/flatnotes/auth.py new file mode 100644 index 0000000..bc01357 --- /dev/null +++ b/flatnotes/auth.py @@ -0,0 +1,40 @@ +import os +from datetime import datetime, timedelta + +from fastapi import Depends, HTTPException +from fastapi.security import OAuth2PasswordBearer +from jose import JWTError, jwt + +FLATNOTES_USERNAME = os.environ["FLATNOTES_USERNAME"] +FLATNOTES_PASSWORD = os.environ["FLATNOTES_PASSWORD"] + +JWT_SECRET_KEY = os.environ["FLATNOTES_SECRET_KEY"] +JWT_EXPIRE_DAYS = int(os.environ.get("FLATNOTES_SESSION_EXPIRY_DAYS", 30)) +JWT_ALGORITHM = "HS256" + +oauth2_scheme = OAuth2PasswordBearer(tokenUrl="api/token") + + +def create_access_token(data: dict): + to_encode = data.copy() + expiry_datetime = datetime.utcnow() + timedelta(days=JWT_EXPIRE_DAYS) + to_encode.update({"exp": expiry_datetime}) + encoded_jwt = jwt.encode( + to_encode, JWT_SECRET_KEY, algorithm=JWT_ALGORITHM + ) + return encoded_jwt + + +async def validate_token(token: str = Depends(oauth2_scheme)): + try: + payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[JWT_ALGORITHM]) + username = payload.get("sub") + if username is None or username.lower() != FLATNOTES_USERNAME.lower(): + raise ValueError + return FLATNOTES_USERNAME + except: + raise HTTPException( + status_code=401, + detail="Invalid authentication credentials", + headers={"WWW-Authenticate": "Bearer"}, + ) diff --git a/flatnotes/main.py b/flatnotes/main.py index 4ac174d..1258bf7 100644 --- a/flatnotes/main.py +++ b/flatnotes/main.py @@ -1,18 +1,24 @@ import logging import os -from typing import Dict, List, Optional - -from fastapi import FastAPI -from fastapi.responses import HTMLResponse -from fastapi.staticfiles import StaticFiles +from typing import List +from auth import ( + FLATNOTES_PASSWORD, + FLATNOTES_USERNAME, + validate_token, + create_access_token, +) from error_responses import ( file_exists_response, file_not_found_response, filename_contains_path_response, ) -from flatnotes import FilenameContainsPathError, Flatnotes, Note, NoteHit -from helpers import CamelCaseBaseModel +from fastapi import Depends, FastAPI, HTTPException +from fastapi.responses import HTMLResponse +from fastapi.staticfiles import StaticFiles +from models import LoginModel, NoteHitModel, NoteModel, NotePatchModel + +from flatnotes import FilenameContainsPathError, Flatnotes, Note logging.basicConfig( format="%(asctime)s [%(levelname)s]: %(message)s", @@ -22,43 +28,20 @@ logger = logging.getLogger() logger.setLevel(os.environ.get("LOGLEVEL", "INFO").upper()) app = FastAPI() - flatnotes = Flatnotes(os.environ["FLATNOTES_PATH"]) -class NoteModel(CamelCaseBaseModel): - filename: str - last_modified: int - content: Optional[str] - - @classmethod - def dump(cls, note: Note, include_content: bool = True) -> Dict: - return { - "filename": note.filename, - "lastModified": note.last_modified, - "content": note.content if include_content else None, - } - - -class NotePatchModel(CamelCaseBaseModel): - new_filename: Optional[str] - new_content: Optional[str] - - -class NoteHitModel(CamelCaseBaseModel): - filename: str - last_modified: int - title_highlights: Optional[str] - content_highlights: Optional[str] - - @classmethod - def dump(self, note_hit: NoteHit) -> Dict: - return { - "filename": note_hit.filename, - "lastModified": note_hit.last_modified, - "titleHighlights": note_hit.title_highlights, - "contentHighlights": note_hit.content_highlights, - } +@app.post("/api/token") +async def token(data: LoginModel): + if ( + data.username != FLATNOTES_USERNAME + or data.password != FLATNOTES_PASSWORD + ): + raise HTTPException( + status_code=400, detail="Incorrect username or password" + ) + access_token = create_access_token(data={"sub": FLATNOTES_USERNAME}) + return {"access_token": access_token, "token_type": "bearer"} @app.get("/") @@ -69,7 +52,9 @@ async def root(): @app.get("/api/notes", response_model=List[NoteModel]) -async def get_notes(include_content: bool = False): +async def get_notes( + include_content: bool = False, _: str = Depends(validate_token) +): """Get all notes.""" return [ NoteModel.dump(note, include_content=include_content) @@ -78,7 +63,9 @@ async def get_notes(include_content: bool = False): @app.post("/api/notes", response_model=NoteModel) -async def post_note(filename: str, content: str): +async def post_note( + filename: str, content: str, _: str = Depends(validate_token) +): """Create a new note.""" try: note = Note(flatnotes, filename, new=True) @@ -91,7 +78,11 @@ async def post_note(filename: str, content: str): @app.get("/api/notes/{filename}", response_model=NoteModel) -async def get_note(filename: str, include_content: bool = True): +async def get_note( + filename: str, + include_content: bool = True, + _: str = Depends(validate_token), +): """Get a specific note.""" try: note = Note(flatnotes, filename) @@ -103,7 +94,9 @@ async def get_note(filename: str, include_content: bool = True): @app.patch("/api/notes/{filename}", response_model=NoteModel) -async def patch_note(filename: str, new_data: NotePatchModel): +async def patch_note( + filename: str, new_data: NotePatchModel, _: str = Depends(validate_token) +): try: note = Note(flatnotes, filename) if new_data.new_filename is not None: @@ -118,7 +111,7 @@ async def patch_note(filename: str, new_data: NotePatchModel): @app.delete("/api/notes/{filename}") -async def delete_note(filename: str): +async def delete_note(filename: str, _: str = Depends(validate_token)): try: note = Note(flatnotes, filename) note.delete() @@ -129,7 +122,7 @@ async def delete_note(filename: str): @app.get("/api/search", response_model=List[NoteHitModel]) -async def search(term: str): +async def search(term: str, _: str = Depends(validate_token)): """Perform a full text search for a note.""" return [NoteHitModel.dump(note_hit) for note_hit in flatnotes.search(term)] diff --git a/flatnotes/models.py b/flatnotes/models.py new file mode 100644 index 0000000..e71b090 --- /dev/null +++ b/flatnotes/models.py @@ -0,0 +1,45 @@ +from typing import Dict, Optional + +from helpers import CamelCaseBaseModel + +from flatnotes import Note, NoteHit + + +class LoginModel(CamelCaseBaseModel): + username: str + password: str + + +class NoteModel(CamelCaseBaseModel): + filename: str + last_modified: int + content: Optional[str] + + @classmethod + def dump(cls, note: Note, include_content: bool = True) -> Dict: + return { + "filename": note.filename, + "lastModified": note.last_modified, + "content": note.content if include_content else None, + } + + +class NotePatchModel(CamelCaseBaseModel): + new_filename: Optional[str] + new_content: Optional[str] + + +class NoteHitModel(CamelCaseBaseModel): + filename: str + last_modified: int + title_highlights: Optional[str] + content_highlights: Optional[str] + + @classmethod + def dump(self, note_hit: NoteHit) -> Dict: + return { + "filename": note_hit.filename, + "lastModified": note_hit.last_modified, + "titleHighlights": note_hit.title_highlights, + "contentHighlights": note_hit.content_highlights, + } diff --git a/flatnotes/src/api.js b/flatnotes/src/api.js new file mode 100644 index 0000000..b1912cf --- /dev/null +++ b/flatnotes/src/api.js @@ -0,0 +1,34 @@ +import axios from "axios"; +import EventBus from "./eventBus.js"; + +const api = axios.create(); + +api.interceptors.request.use( + function(config) { + if (config.url !== "/api/token") { + let token = sessionStorage.getItem("token"); + config.headers.Authorization = `Bearer ${token}`; + } + return config; + }, + function(error) { + return Promise.reject(error); + } +); + +api.interceptors.response.use( + function(response) { + return response; + }, + function(error) { + if ( + typeof error.response !== "undefined" && + error.response.status === 401 + ) { + EventBus.$emit("logout"); + } + return Promise.reject(error); + } +); + +export default api; diff --git a/flatnotes/src/components/App.js b/flatnotes/src/components/App.js index c40229b..e40dbeb 100644 --- a/flatnotes/src/components/App.js +++ b/flatnotes/src/components/App.js @@ -2,9 +2,10 @@ import "@toast-ui/editor/dist/toastui-editor.css"; import "@toast-ui/editor/dist/toastui-editor-viewer.css"; import { Editor } from "@toast-ui/vue-editor"; import { Viewer } from "@toast-ui/vue-editor"; -import axios from "axios"; +import api from "../api"; import { Note, SearchResult } from "./classes"; +import EventBus from "../eventBus"; export default { components: { @@ -14,6 +15,10 @@ export default { data: function() { return { + loggedIn: false, + usernameInput: null, + passwordInput: null, + rememberMeInput: false, notes: [], searchTerm: null, searchTimeout: null, @@ -25,8 +30,12 @@ export default { computed: { currentView: function() { + // 4 - Login + if (this.loggedIn == false) { + return 4; + } // 3 - Edit Note - if (this.currentNote && this.editMode) { + else if (this.currentNote && this.editMode) { return 3; } // 2 - View Note @@ -62,17 +71,45 @@ export default { }, methods: { + login: function() { + let parent = this; + api + .post("/api/token", { + username: this.usernameInput, + password: this.passwordInput, + }) + .then(function(response) { + sessionStorage.setItem("token", response.data.access_token); + if (parent.rememberMeInput == true) { + localStorage.setItem("token", response.data.access_token); + } + parent.loggedIn = true; + parent.getNotes(); + }) + .finally(function() { + parent.usernameInput = null; + parent.passwordInput = null; + parent.rememberMeInput = false; + }); + }, + + logout: function() { + sessionStorage.removeItem("token"); + localStorage.removeItem("token"); + this.loggedIn = false; + }, + getNotes: function() { - parent = this; + let parent = this; parent.notes = []; - axios.get("/api/notes").then(function(response) { + api.get("/api/notes").then(function(response) { response.data.forEach(function(note) { parent.notes.push(new Note(note.filename, note.lastModified)); }); }); }, - clearSearchTimeout: function(params) { + clearSearchTimeout: function() { if (this.searchTimeout != null) { clearTimeout(this.searchTimeout); } @@ -84,13 +121,13 @@ export default { }, search: function() { - parent = this; + let parent = this; this.clearSearchTimeout(); - this.searchResults = []; if (this.searchTerm) { - axios + api .get("/api/search", { params: { term: this.searchTerm } }) .then(function(response) { + parent.searchResults = []; response.data.forEach(function(result) { parent.searchResults.push( new SearchResult( @@ -106,8 +143,8 @@ export default { }, loadNote: function(filename) { - parent = this; - axios.get(`/api/notes/${filename}`).then(function(response) { + let parent = this; + api.get(`/api/notes/${filename}`).then(function(response) { parent.currentNote = response.data; }); }, @@ -118,10 +155,10 @@ export default { }, saveNote: function() { - parent = this; + let parent = this; let newContent = this.$refs.toastUiEditor.invoke("getMarkdown"); if (newContent != this.currentNote.content) { - axios + api .patch(`/api/notes/${this.currentNote.filename}`, { newContent: newContent, }) @@ -136,6 +173,13 @@ export default { }, created: function() { - this.getNotes(); + EventBus.$on("logout", this.logout); + + let token = localStorage.getItem("token"); + if (token != null) { + sessionStorage.setItem("token", token); + this.loggedIn = true; + this.getNotes(); + } }, }; diff --git a/flatnotes/src/components/App.vue b/flatnotes/src/components/App.vue index 6f08ec5..03e8080 100644 --- a/flatnotes/src/components/App.vue +++ b/flatnotes/src/components/App.vue @@ -7,15 +7,22 @@ -
+
+ + +
+ +
+
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ -
+
@@ -70,16 +113,17 @@
-
- -
+
+
+ +
+
diff --git a/flatnotes/src/eventBus.js b/flatnotes/src/eventBus.js new file mode 100644 index 0000000..dc7e25b --- /dev/null +++ b/flatnotes/src/eventBus.js @@ -0,0 +1,3 @@ +import Vue from "vue"; + +export default new Vue();