From 2f00144e2309910db97002054e22ab8e30ee84d2 Mon Sep 17 00:00:00 2001 From: Wenyi Xu Date: Tue, 30 Jul 2024 14:13:15 +0800 Subject: [PATCH] 295 implement token based auth (#296) * Support user login. (#294) * Support user login. (#294) * Support user login. (#294) * 291 support token based auth (#293) * 1. Added a new file "auth.py" to the "app/auth" directory, which contains a function "auth_required" that is used as a decorator for route functions that require authentication. This function checks for the presence of credentials in the request's authorization header and verifies them against the user model in the database. If the credentials are valid, the user object is added to the Flask's "g" object and the route function is called. If the credentials are missing or invalid, a 401 Unauthorized response is returned. 2. Updated the "notebook.py" file in the "app/routes" directory to import the "auth_required" function from the "auth.py" file. This function is then used as a decorator for the "get_all_notebooks" route function, which requires authentication. This ensures that only authenticated users can access the route. 3. Added the "@auth_required" decorator above the "get_all_notebooks" route function in the "notebook.py" file. This decorator ensures that the route function is only called if the user is authenticated. If the user is not authenticated, a 401 Unauthorized response is returned. 4. Added a new test case to the "test_notebook_route.py" file to test the authentication functionality of the "get_all_notebooks" route. This test case sends a GET request to the route without providing any credentials and asserts that the response status code is 401 Unauthorized. 5. Updated the commit message to include the changes made in the previous commits. * 291 support token based auth (#293) * 1. Added a new file "auth.py" to the "app/auth" directory, which contains a function "auth_required" that is used as a decorator for route functions that require authentication. This function checks for the presence of credentials in the request's authorization header and verifies them against the user model in the database. If the credentials are valid, the user object is added to the Flask's "g" object and the route function is called. If the credentials are missing or invalid, a 401 Unauthorized response is returned. 2. Updated the "notebook.py" file in the "app/routes" directory to import the "auth_required" function from the "auth.py" file. This function is then used as a decorator for the "get_all_notebooks" route function, which requires authentication. This ensures that only authenticated users can access the route. 3. Added the "@auth_required" decorator above the "get_all_notebooks" route function in the "notebook.py" file. This decorator ensures that the route function is only called if the user is authenticated. If the user is not authenticated, a 401 Unauthorized response is returned. 4. Added a new test case to the "test_notebook_route.py" file to test the authentication functionality of the "get_all_notebooks" route. This test case sends a GET request to the route without providing any credentials and asserts that the response status code is 401 Unauthorized. 5. Updated the commit message to include the changes made in the previous commits. * 291 support token based auth (#293) * 1. Added a new file "auth.py" to the "app/auth" directory, which contains a function "auth_required" that is used as a decorator for route functions that require authentication. This function checks for the presence of credentials in the request's authorization header and verifies them against the user model in the database. If the credentials are valid, the user object is added to the Flask's "g" object and the route function is called. If the credentials are missing or invalid, a 401 Unauthorized response is returned. 2. Updated the "notebook.py" file in the "app/routes" directory to import the "auth_required" function from the "auth.py" file. This function is then used as a decorator for the "get_all_notebooks" route function, which requires authentication. This ensures that only authenticated users can access the route. 3. Added the "@auth_required" decorator above the "get_all_notebooks" route function in the "notebook.py" file. This decorator ensures that the route function is only called if the user is authenticated. If the user is not authenticated, a 401 Unauthorized response is returned. 4. Added a new test case to the "test_notebook_route.py" file to test the authentication functionality of the "get_all_notebooks" route. This test case sends a GET request to the route without providing any credentials and asserts that the response status code is 401 Unauthorized. 5. Updated the commit message to include the changes made in the previous commits. * 291 support token based auth (#293) * 1. Added a new file "auth.py" to the "app/auth" directory, which contains a function "auth_required" that is used as a decorator for route functions that require authentication. This function checks for the presence of credentials in the request's authorization header and verifies them against the user model in the database. If the credentials are valid, the user object is added to the Flask's "g" object and the route function is called. If the credentials are missing or invalid, a 401 Unauthorized response is returned. 2. Updated the "notebook.py" file in the "app/routes" directory to import the "auth_required" function from the "auth.py" file. This function is then used as a decorator for the "get_all_notebooks" route function, which requires authentication. This ensures that only authenticated users can access the route. 3. Added the "@auth_required" decorator above the "get_all_notebooks" route function in the "notebook.py" file. This decorator ensures that the route function is only called if the user is authenticated. If the user is not authenticated, a 401 Unauthorized response is returned. 4. Added a new test case to the "test_notebook_route.py" file to test the authentication functionality of the "get_all_notebooks" route. This test case sends a GET request to the route without providing any credentials and asserts that the response status code is 401 Unauthorized. 5. Updated the commit message to include the changes made in the previous commits. * Support user login. (#294) * Support user login. (#294) * Fix authentication error in test_notebook_route.py * Refactor authentication logic and update API calls to use token-based authentication --- .gitignore | 4 + server/app/auth/auth.py | 13 +++- server/app/routes/login.py | 16 ++-- server/app/routes/notebook.py | 25 +++++-- server/requirements.txt | 3 +- server/run.py | 11 +++ server/tests/routes/test_notebook_route.py | 86 ++++++++++++++-------- webapp/src/components/auth/LoginForm.js | 3 + webapp/src/models/NotebookModel.js | 51 ++++--------- 9 files changed, 127 insertions(+), 85 deletions(-) diff --git a/.gitignore b/.gitignore index b20c1de..4f06fc1 100644 --- a/.gitignore +++ b/.gitignore @@ -209,3 +209,7 @@ webapp/yarn-error.log* webapp/node_modules/* node_modules/* .DS_Store + + +server/.DS_Store +server/app_secrets.py diff --git a/server/app/auth/auth.py b/server/app/auth/auth.py index a597ebb..1505826 100644 --- a/server/app/auth/auth.py +++ b/server/app/auth/auth.py @@ -1,9 +1,10 @@ from flask import request, Response, g from app.models.user import UserModel +from flask_jwt_extended import get_jwt_identity from functools import wraps import json -def auth_required(f): +def password_required(f): @wraps(f) def decorated(*args, **kwargs): auth = request.authorization @@ -22,4 +23,12 @@ def decorated(*args, **kwargs): g.user = user return f(*args, **kwargs) - return decorated \ No newline at end of file + return decorated + +def identify_user(f): + @wraps(f) + def decorated(*args, **kwargs): + user_name = get_jwt_identity() + g.user = UserModel.query.filter_by(name=user_name).first() + return f(*args, **kwargs) + return decorated diff --git a/server/app/routes/login.py b/server/app/routes/login.py index adf952c..870f5c3 100644 --- a/server/app/routes/login.py +++ b/server/app/routes/login.py @@ -1,6 +1,6 @@ from flask import Blueprint, Response, g -from app.services.user import User -from app.auth.auth import auth_required +from flask_jwt_extended import create_access_token +from app.auth.auth import password_required import logging import json @@ -9,16 +9,16 @@ logging.basicConfig(level=logging.INFO) @login_blueprint.route('/login', methods=['POST']) -@auth_required +@password_required def login(): logging.info(f"Logging in user: {g.user.name}") - - # name = data.get('name', None) - # password = data.get('password', None) - # return User.validate_user_by_name(name=name, password=password) + access_token = create_access_token(identity=g.user.name) return Response( - response=json.dumps({'message': 'Login successful'}), + response=json.dumps({ + 'message': 'Login successful', + 'access_token': access_token + }), status=200 ) diff --git a/server/app/routes/notebook.py b/server/app/routes/notebook.py index 5c2e51f..97eda4e 100644 --- a/server/app/routes/notebook.py +++ b/server/app/routes/notebook.py @@ -1,6 +1,7 @@ from flask import Blueprint, jsonify, request, g from app.services.notebook import Notebook -from app.auth.auth import auth_required +from flask_jwt_extended import jwt_required +from app.auth.auth import identify_user import logging notebook_blueprint = Blueprint('notebook', __name__) @@ -16,18 +17,22 @@ def notebook(): ) @notebook_blueprint.route('/notebook/all', methods=['GET']) -@auth_required +@jwt_required() +@identify_user def get_all_notebooks(): + logging.info(f"Getting all notebooks by user: {g.user.name}") return Notebook.get_all_notebooks() @notebook_blueprint.route('/notebook/', methods=['GET']) -@auth_required +@jwt_required() +@identify_user def get_notebook_by_path(notebook_path): logging.info(f"Getting notebook with path: {notebook_path} by user: {g.user.name}") return Notebook.get_notebook_by_path(notebook_path=notebook_path) @notebook_blueprint.route('/notebook', methods=['POST']) -@auth_required +@jwt_required() +@identify_user def create_notebook(): data = request.get_json() notebook_name = data.get('name', None) @@ -36,7 +41,8 @@ def create_notebook(): return Notebook.create_notebook_with_init_cells(notebook_name=notebook_name, notebook_path=notebook_path) @notebook_blueprint.route('/notebook/', methods=['PUT']) -@auth_required +@jwt_required() +@identify_user def update_notebook(notebook_path): data = request.get_json() content = data.get('content', None) @@ -44,13 +50,15 @@ def update_notebook(notebook_path): return Notebook.update_notebook(notebook_path=notebook_path, content=content) @notebook_blueprint.route('/notebook/', methods=['DELETE']) -@auth_required +@jwt_required() +@identify_user def delete_notebook(notebook_path): logging.info(f"Deleting notebook with path: {notebook_path}") return Notebook.delete_notebook_by_path(notebook_path=notebook_path) @notebook_blueprint.route('/notebook/', methods=['PATCH']) -@auth_required +@jwt_required() +@identify_user def rename_or_move_notebook(notebook_path): data = request.get_json() if 'newName' in data: @@ -63,7 +71,8 @@ def rename_or_move_notebook(notebook_path): return Notebook.move_notebook(notebook_path=notebook_path, new_notebook_path=new_notebook_path) @notebook_blueprint.route('/notebook/spark_app/', methods=['GET']) -@auth_required +@jwt_required() +@identify_user def get_spark_app_by_notebook_path(notebook_path): logging.info(f"Get spark apps by notebook path: {notebook_path}") return Notebook.get_spark_app_by_notebook_path(notebook_path) diff --git a/server/requirements.txt b/server/requirements.txt index 98c413b..d86bdd8 100644 --- a/server/requirements.txt +++ b/server/requirements.txt @@ -2,4 +2,5 @@ Flask==3.0.3 Flask-Cors==4.0.1 requests==2.32.2 Flask-SQLAlchemy==3.1.1 -psycopg2-binary==2.9.9 \ No newline at end of file +psycopg2-binary==2.9.9 +flask-jwt-extended==4.6.0 \ No newline at end of file diff --git a/server/run.py b/server/run.py index 931a7aa..c815d37 100644 --- a/server/run.py +++ b/server/run.py @@ -8,6 +8,7 @@ from app.routes.kernel import kernel_blueprint from app.routes.spark_app import spark_app_blueprint from app.routes.login import login_blueprint +from flask_jwt_extended import JWTManager from config import DevelopmentConfig, IntegrationTestingConfig, TestingConfig def create_app(): @@ -19,6 +20,15 @@ def create_app(): elif os.environ.get('ENV', 'development') == 'integration': app.config.from_object(IntegrationTestingConfig) + # Set the secret key for JWT + try: + from app_secrets import JWT_SECRET_KEY + except ImportError: + JWT_SECRET_KEY = os.environ.get('JWT_SECRET_KEY', 'default_secret_key') + + app.config['JWT_SECRET_KEY'] = JWT_SECRET_KEY + jwt = JWTManager(app) + db.init_app(app) allowed_origins = ["http://localhost:5001", "http://localhost:3000"] @@ -38,5 +48,6 @@ def create_app(): app.register_blueprint(login_blueprint) + if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5002) diff --git a/server/tests/routes/test_notebook_route.py b/server/tests/routes/test_notebook_route.py index d1c1f20..b4f1ded 100644 --- a/server/tests/routes/test_notebook_route.py +++ b/server/tests/routes/test_notebook_route.py @@ -4,14 +4,17 @@ from database import db from run import create_app from app.routes.notebook import notebook_blueprint +from app.routes.login import login_blueprint from app.services.directory import Directory from app.models.user import UserModel + class NotebookRouteTestCase(unittest.TestCase): def setUp(self): self.app = create_app() self.app.register_blueprint(notebook_blueprint) + self.app.register_blueprint(login_blueprint) self.client = self.app.test_client() with self.app.app_context(): db.create_all() @@ -25,13 +28,21 @@ def tearDown(self): db.session.remove() db.drop_all() + def login_and_get_token(self): + with self.app.app_context(): + response = self.client.post('/login', auth=('test_user', 'test_password')) + return json.loads(response.data)['access_token'] + def test_get_all_notebooks(self): with self.app.app_context(): path = '/notebook/all' - auth = ('test_user', 'test_password') + token = self.login_and_get_token() + headers = { + 'Authorization': f'Bearer {token}', + } response = self.client.get( path, - auth=auth + headers=headers ) self.assertEqual(response.status_code, 200) @@ -42,18 +53,17 @@ def test_get_all_notebooks_without_auth(self): path ) self.assertEqual(response.status_code, 401) - self.assertEqual(json.loads(response.data)["message"], 'Missing credentials') def test_get_all_notebooks_with_invalid_auth(self): with self.app.app_context(): path = '/notebook/all' - auth = ('test_user', 'invalid_password') response = self.client.get( path, - auth=auth + headers={ + 'Authorization': 'Bearer invalid_token' + } ) - self.assertEqual(response.status_code, 401) - self.assertEqual(json.loads(response.data)["message"], 'Invalid credentials') + self.assertEqual(response.status_code, 422) def test_get_notebook_by_path(self): with self.app.app_context(): @@ -62,16 +72,19 @@ def test_get_notebook_by_path(self): self.assertEqual(response_1.status_code, 201) # Create notebook - auth = ('test_user', 'test_password') + token = self.login_and_get_token() + headers = { + 'Authorization': f'Bearer {token}', + } data = { "name": "test_notebook", "path": "work/test_get_notebook_by_path_directory" } - response_2 = self.client.post('/notebook', json=data, auth=auth) + response_2 = self.client.post('/notebook', json=data, headers=headers) self.assertEqual(response_2.status_code, 200) # Get notebook - response_3 = self.client.get('/notebook/work/test_get_notebook_by_path_directory/test_notebook.ipynb', auth=auth) + response_3 = self.client.get('/notebook/work/test_get_notebook_by_path_directory/test_notebook.ipynb', headers=headers) self.assertEqual(response_3.status_code, 200) self.assertEqual(json.loads(response_3.data)["name"], 'test_notebook.ipynb') self.assertEqual(json.loads(response_3.data)["path"], 'work/test_get_notebook_by_path_directory/test_notebook.ipynb') @@ -83,12 +96,15 @@ def test_create_notebook(self): self.assertEqual(response_1.status_code, 201) # Create notebook - auth = ('test_user', 'test_password') + token = self.login_and_get_token() + headers = { + 'Authorization': f'Bearer {token}', + } data = { "name": "test_notebook", "path": "work/test_create_notebook_directory" } - response_2 = self.client.post('/notebook', json=data, auth=auth) + response_2 = self.client.post('/notebook', json=data, headers=headers) self.assertEqual(response_2.status_code, 200) self.assertEqual(json.loads(response_2.data)["name"], 'test_notebook.ipynb') self.assertEqual(json.loads(response_2.data)["path"], 'work/test_create_notebook_directory/test_notebook.ipynb') @@ -100,12 +116,15 @@ def test_update_notebook(self): self.assertEqual(response_1.status_code, 201) # Create notebook - auth = ('test_user', 'test_password') + token = self.login_and_get_token() + headers = { + 'Authorization': f'Bearer {token}', + } data = { "name": "test_notebook", "path": "work/test_update_notebook_directory" } - response_2 = self.client.post('/notebook', json=data, auth=auth) + response_2 = self.client.post('/notebook', json=data, headers=headers) self.assertEqual(response_2.status_code, 200) # Update notebook @@ -130,11 +149,11 @@ def test_update_notebook(self): ] } } - response_3 = self.client.put('/notebook/work/test_update_notebook_directory/test_notebook.ipynb', json=data, auth=auth) + response_3 = self.client.put('/notebook/work/test_update_notebook_directory/test_notebook.ipynb', json=data, headers=headers) self.assertEqual(response_3.status_code, 200) # Check if notebook is updated - response_4 = self.client.get('/notebook/work/test_update_notebook_directory/test_notebook.ipynb', auth=auth) + response_4 = self.client.get('/notebook/work/test_update_notebook_directory/test_notebook.ipynb', headers=headers) self.assertEqual(response_4.status_code, 200) self.assertEqual(json.loads(response_4.data)["content"]["cells"][0]["source"], "print('Hello, World!')") @@ -145,20 +164,23 @@ def test_delete_notebook(self): self.assertEqual(response_1.status_code, 201) # Create notebook - auth = ('test_user', 'test_password') + token = self.login_and_get_token() + headers = { + 'Authorization': f'Bearer {token}', + } data = { "name": "test_notebook", "path": "work/test_delete_notebook_directory" } - response_2 = self.client.post('/notebook', json=data, auth=auth) + response_2 = self.client.post('/notebook', json=data, headers=headers) self.assertEqual(response_2.status_code, 200) # Delete notebook - response_3 = self.client.delete('/notebook/work/test_delete_notebook_directory/test_notebook.ipynb', auth=auth) + response_3 = self.client.delete('/notebook/work/test_delete_notebook_directory/test_notebook.ipynb', headers=headers) self.assertEqual(response_3.status_code, 200) # Check if notebook is deleted - response_4 = self.client.get('/notebook/work/test_delete_notebook_directory/test_notebook.ipynb', auth=auth) + response_4 = self.client.get('/notebook/work/test_delete_notebook_directory/test_notebook.ipynb', headers=headers) self.assertEqual(response_4.status_code, 404) def test_rename_or_move_notebook(self): @@ -168,34 +190,37 @@ def test_rename_or_move_notebook(self): self.assertEqual(response_1.status_code, 201) # Create notebook - auth = ('test_user', 'test_password') + token = self.login_and_get_token() + headers = { + 'Authorization': f'Bearer {token}', + } data = { "name": "test_notebook", "path": "work/test_rename_or_move_notebook_directory" } - response_2 = self.client.post('/notebook', json=data, auth=auth) + response_2 = self.client.post('/notebook', json=data, headers=headers) self.assertEqual(response_2.status_code, 200) # Rename notebook data = { "newName": "new_test_notebook.ipynb" } - response_3 = self.client.patch('/notebook/work/test_rename_or_move_notebook_directory/test_notebook.ipynb', json=data, auth=auth) + response_3 = self.client.patch('/notebook/work/test_rename_or_move_notebook_directory/test_notebook.ipynb', json=data, headers=headers) self.assertEqual(response_3.status_code, 200) # Check if notebook is renamed - response_4 = self.client.get('/notebook/work/test_rename_or_move_notebook_directory/new_test_notebook.ipynb', auth=auth) + response_4 = self.client.get('/notebook/work/test_rename_or_move_notebook_directory/new_test_notebook.ipynb', headers=headers) self.assertEqual(response_4.status_code, 200) # Move notebook data = { "newPath": "work/new_test_notebook.ipynb" } - response_5 = self.client.patch('/notebook/work/test_rename_or_move_notebook_directory/new_test_notebook.ipynb', json=data, auth=auth) + response_5 = self.client.patch('/notebook/work/test_rename_or_move_notebook_directory/new_test_notebook.ipynb', json=data, headers=headers) self.assertEqual(response_5.status_code, 200) # Check if notebook is moved - response_6 = self.client.get('/notebook/work/new_test_notebook.ipynb', auth=auth) + response_6 = self.client.get('/notebook/work/new_test_notebook.ipynb', headers=headers) self.assertEqual(response_6.status_code, 200) def test_get_spark_app_by_notebook_path(self): @@ -205,14 +230,17 @@ def test_get_spark_app_by_notebook_path(self): self.assertEqual(response_1.status_code, 201) # Create notebook - auth = ('test_user', 'test_password') + token = self.login_and_get_token() + headers = { + 'Authorization': f'Bearer {token}', + } data = { "name": "test_notebook", "path": "work/test_get_spark_app_by_notebook_path_directory" } - response_2 = self.client.post('/notebook', json=data, auth=auth) + response_2 = self.client.post('/notebook', json=data, headers=headers) self.assertEqual(response_2.status_code, 200) # Get spark app by notebook path - response_3 = self.client.get('/notebook/spark_app/work/test_get_spark_app_by_notebook_path_directory/test_notebook.ipynb', auth=auth) + response_3 = self.client.get('/notebook/spark_app/work/test_get_spark_app_by_notebook_path_directory/test_notebook.ipynb', headers=headers) self.assertEqual(response_3.status_code, 200) \ No newline at end of file diff --git a/webapp/src/components/auth/LoginForm.js b/webapp/src/components/auth/LoginForm.js index 29e4fe4..26129eb 100644 --- a/webapp/src/components/auth/LoginForm.js +++ b/webapp/src/components/auth/LoginForm.js @@ -25,6 +25,9 @@ function LoginForm({ onLogin }) { // You might want to save the username and password in the session storage here // And redirect the user to the home page console.log('Logged in successfully'); + const data = await response.json(); + const token = data.access_token; + sessionStorage.setItem('token', token); onLogin(username, password); } else { // The login failed diff --git a/webapp/src/models/NotebookModel.js b/webapp/src/models/NotebookModel.js index 7a70beb..fc92622 100644 --- a/webapp/src/models/NotebookModel.js +++ b/webapp/src/models/NotebookModel.js @@ -41,16 +41,13 @@ class NotebookModel { }; static async fetchNotebook(path = '') { - // TODO: Replace with actual username and password - const username = config.username; - const password = config.password; + const token = sessionStorage.getItem('token'); - const credentials = btoa(`${username}:${password}`); const response = await fetch(`${config.serverBaseUrl}/notebook/` + path, { method: 'GET', headers: { 'Content-Type': 'application/json', - 'Authorization': `Basic ${credentials}`, + 'Authorization': `Bearer ${token}`, } }); @@ -62,16 +59,13 @@ class NotebookModel { } static async createNotebook(path = '', notebookName = '') { - // TODO: Replace with actual username and password - const username = config.username; - const password = config.password; + const token = sessionStorage.getItem('token'); - const credentials = btoa(`${username}:${password}`); const response = await fetch(`${config.serverBaseUrl}/notebook`, { method: 'POST', headers: { 'Content-Type': 'application/json', - 'Authorization': `Basic ${credentials}`, + 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ 'name': notebookName, @@ -88,16 +82,13 @@ class NotebookModel { }; static async deleteNotebook(path = '') { - // TODO: Replace with actual username and password - const username = config.username; - const password = config.password; + const token = sessionStorage.getItem('token'); - const credentials = btoa(`${username}:${password}`); const response = await fetch(`${config.serverBaseUrl}/notebook/` + path, { method: 'DELETE', headers: { 'Content-Type': 'application/json', - 'Authorization': `Basic ${credentials}`, + 'Authorization': `Bearer ${token}`, } }); @@ -110,11 +101,7 @@ class NotebookModel { }; static async updateNotebook(path = '', content = {}) { - // TODO: Replace with actual username and password - const username = config.username; - const password = config.password; - - const credentials = btoa(`${username}:${password}`); + const token = sessionStorage.getItem('token'); const updatedContent = { ...content }; @@ -132,7 +119,7 @@ class NotebookModel { method: 'PUT', headers: { 'Content-Type': 'application/json', - 'Authorization': `Basic ${credentials}`, + 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ 'content': updatedContent @@ -147,11 +134,7 @@ class NotebookModel { }; static async renameNotebook(path = '', newName = '') { - // TODO: Replace with actual username and password - const username = config.username; - const password = config.password; - - const credentials = btoa(`${username}:${password}`); + const token = sessionStorage.getItem('token'); console.log("Renaming notebook at path:", path, "to:", newName); @@ -159,7 +142,7 @@ class NotebookModel { method: 'PATCH', headers: { 'Content-Type': 'application/json', - 'Authorization': `Basic ${credentials}`, + 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ 'newName': newName @@ -174,16 +157,13 @@ class NotebookModel { }; static async getSparkApps(notebookPath = '') { - // TODO: Replace with actual username and password - const username = config.username; - const password = config.password; + const token = sessionStorage.getItem('token'); - const credentials = btoa(`${username}:${password}`); const response = await fetch(`${config.serverBaseUrl}/notebook/spark_app/${notebookPath}`, { method: 'GET', headers: { 'Content-Type': 'application/json', - 'Authorization': `Basic ${credentials}`, + 'Authorization': `Bearer ${token}`, } }); @@ -197,16 +177,13 @@ class NotebookModel { static async moveNotebook(path = '', destination = '') { console.log("Moving notebook at path:", path, "to:", destination); - // TODO: Replace with actual username and password - const username = config.username; - const password = config.password; + const token = sessionStorage.getItem('token'); - const credentials = btoa(`${username}:${password}`); const response = await fetch(`${config.serverBaseUrl}/notebook/` + path, { method: 'PATCH', headers: { 'Content-Type': 'application/json', - 'Authorization': `Basic ${credentials}`, + 'Authorization': `Bearer ${token}`, }, body: JSON.stringify({ 'newPath': destination