From 25f315c24f80b7c5414b7c4e7b1357b566ca8999 Mon Sep 17 00:00:00 2001 From: starnakin Date: Fri, 1 Dec 2023 01:29:56 +0100 Subject: [PATCH] add: delete and edit accounts page --- accounts/tests/__init__.py | 1 + accounts/tests/delete.py | 9 +- accounts/views/delete.py | 7 ++ accounts/views/edit.py | 3 + frontend/static/js/api/account.js | 59 ++++++++++ frontend/static/js/api/accounts.js | 15 --- frontend/static/js/api/client.js | 41 ++++++- frontend/static/js/index.js | 2 + frontend/static/js/views/HomeView.js | 1 + frontend/static/js/views/MeView.js | 103 ++++++++++++++++++ .../static/js/views/accounts/RegisterView.js | 10 +- trancendence/settings.py | 4 +- 12 files changed, 232 insertions(+), 23 deletions(-) create mode 100644 frontend/static/js/api/account.js delete mode 100644 frontend/static/js/api/accounts.js create mode 100644 frontend/static/js/views/MeView.js diff --git a/accounts/tests/__init__.py b/accounts/tests/__init__.py index 4187043..80d6cf2 100644 --- a/accounts/tests/__init__.py +++ b/accounts/tests/__init__.py @@ -1,4 +1,5 @@ from .register import * from .login import * +from .logout import * from .edit import * from .delete import * \ No newline at end of file diff --git a/accounts/tests/delete.py b/accounts/tests/delete.py index f7cfc47..438d6d1 100644 --- a/accounts/tests/delete.py +++ b/accounts/tests/delete.py @@ -21,12 +21,17 @@ class DeleteTest(TestCase): def test_normal_delete(self): - response: HttpResponse = self.client.delete(self.url) + response: HttpResponse = self.client.delete(self.url, {"password": self.password}, content_type='application/json') response_text: str = response.content.decode("utf-8") self.assertEqual(response_text, '"user deleted"') + def test_wrong_pass(self): + response: HttpResponse = self.client.delete(self.url, {"password": "cacaman a frapper"}, content_type='application/json') + errors: dict = eval(response.content) + self.assertDictEqual(errors, {"password": ["Password wrong."]}) + def test_no_logged(self): self.client.logout() - response: HttpResponse = self.client.post(self.url) + response: HttpResponse = self.client.delete(self.url, {"password": self.password}, content_type='application/json') errors: dict = eval(response.content) self.assertDictEqual(errors, {"detail":"Authentication credentials were not provided."}) \ No newline at end of file diff --git a/accounts/views/delete.py b/accounts/views/delete.py index 0a3265c..30b4c7c 100644 --- a/accounts/views/delete.py +++ b/accounts/views/delete.py @@ -8,5 +8,12 @@ class DeleteView(APIView): permission_classes = (permissions.IsAuthenticated,) authentication_classes = (SessionAuthentication,) def delete(self, request: HttpRequest): + data: dict = request.data + + password: str = data["password"] + if (password is None): + return Response({"password": ["This field may not be blank."]}) + if (request.user.check_password(password) == False): + return Response({"password": ["Password wrong."]}) request.user.delete() return Response("user deleted", status=status.HTTP_200_OK) \ No newline at end of file diff --git a/accounts/views/edit.py b/accounts/views/edit.py index 8325b06..7741e3f 100644 --- a/accounts/views/edit.py +++ b/accounts/views/edit.py @@ -12,6 +12,9 @@ class EditView(APIView): permission_classes = (permissions.IsAuthenticated,) authentication_classes = (SessionAuthentication,) + def get(self, request: HttpRequest): + return Response({"username": request.user.username}) + def patch(self, request: HttpRequest): data: dict = request.data diff --git a/frontend/static/js/api/account.js b/frontend/static/js/api/account.js new file mode 100644 index 0000000..d2b590a --- /dev/null +++ b/frontend/static/js/api/account.js @@ -0,0 +1,59 @@ +class Account +{ + constructor (client) + { + this.client = client; + } + + async create(username, password) + { + let response = await this.client._post("/api/accounts/register", {username: username, password: password}); + let response_data = await response.json() + + if (response_data == "user created") + { + this._logged = true; + return null; + } + return response_data + } + + async delete(password) + { + let response = await this.client._delete("/api/accounts/delete", {password: password}); + let response_data = await response.json(); + + if (response_data === "user deleted") + this.client._logged = false; + return response_data; + } + + async get() + { + let response = await this.client._get("/api/accounts/edit"); + let response_data = await response.json(); + + if (JSON.stringify(response_data) == JSON.stringify({'detail': 'Authentication credentials were not provided.'})) + { + console.log("error, client is not logged"); + return null; + } + return response_data; + } + + async update(data, password) + { + data.password = password; + let response = await this.client._patch_json("/api/accounts/edit", data); + let response_data = await response.json(); + + if (JSON.stringify(response_data) == JSON.stringify({'detail': 'Authentication credentials were not provided.'})) + { + console.log("error, client is not logged"); + return null; + } + return response_data; + } +} + +export { Account } \ No newline at end of file diff --git a/frontend/static/js/api/accounts.js b/frontend/static/js/api/accounts.js deleted file mode 100644 index 154a563..0000000 --- a/frontend/static/js/api/accounts.js +++ /dev/null @@ -1,15 +0,0 @@ -class Accounts -{ - constructor (client) - { - this.client = client; - } - - async create(username, password) - { - let response = await this.client._post("/api/accounts/register", {username: username, password: password}); - return response - } -} - -export { Accounts } \ No newline at end of file diff --git a/frontend/static/js/api/client.js b/frontend/static/js/api/client.js index b0a5439..fa0d94d 100644 --- a/frontend/static/js/api/client.js +++ b/frontend/static/js/api/client.js @@ -1,11 +1,21 @@ -import { Accounts } from "./accounts.js"; +import { Account } from "./account.js"; + +function getCookie(name) +{ + let cookie = {}; + document.cookie.split(';').forEach(function(el) { + let split = el.split('='); + cookie[split[0].trim()] = split.slice(1).join("="); + }) + return cookie[name]; +} class Client { constructor(url) { this._url = url; - this.accounts = new Accounts(this); + this.account = new Account(this); this._logged = undefined; } @@ -30,6 +40,33 @@ class Client method: "POST", headers: { "Content-Type": "application/json", + "X-CSRFToken": getCookie("csrftoken"), + }, + body: JSON.stringify(data), + }); + return response; + } + + async _delete(uri, data) + { + let response = await fetch(this._url + uri, { + method: "DELETE", + headers: { + "Content-Type": "application/json", + "X-CSRFToken": getCookie("csrftoken"), + }, + body: JSON.stringify(data), + }); + return response; + } + + async _patch_json(uri, data) + { + let response = await fetch(this._url + uri, { + method: "PATCH", + headers: { + "X-CSRFToken": getCookie("csrftoken"), + "Content-Type": "application/json", }, body: JSON.stringify(data), }); diff --git a/frontend/static/js/index.js b/frontend/static/js/index.js index 47255df..e923833 100644 --- a/frontend/static/js/index.js +++ b/frontend/static/js/index.js @@ -10,6 +10,7 @@ import LogoutView from "./views/accounts/LogoutView.js"; import { Client } from "./api/client.js"; import AbstractRedirectView from "./views/AbstractRedirectView.js"; +import MeView from "./views/MeView.js"; let client = new Client(location.protocol + "//" + location.host) @@ -42,6 +43,7 @@ const router = async (uri = "") => { { path: "/register", view: RegisterView }, { path: "/chat", view: Chat }, { path: "/home", view: HomeView }, + { path: "/me", view: MeView }, ]; // Test each route for potential match diff --git a/frontend/static/js/views/HomeView.js b/frontend/static/js/views/HomeView.js index 5f9f3fb..d9fd194 100644 --- a/frontend/static/js/views/HomeView.js +++ b/frontend/static/js/views/HomeView.js @@ -9,6 +9,7 @@ export default class extends AbstractAuthentificateView { async getHtml() { return `

HOME

+ Me Logout `; } diff --git a/frontend/static/js/views/MeView.js b/frontend/static/js/views/MeView.js new file mode 100644 index 0000000..d20ce8c --- /dev/null +++ b/frontend/static/js/views/MeView.js @@ -0,0 +1,103 @@ +import { client, navigateTo } from "../index.js"; +import AbstractAuthentificateView from "./AbstractAuthentifiedView.js"; + +export default class extends AbstractAuthentificateView +{ + constructor(params) + { + super(params, "Me"); + } + + async postInit() + { + if (this.fill() === null) + return; + document.getElementById("save-button").onclick = this.save; + document.getElementById("delete-button").onclick = this.delete_accounts; + } + + async fill() + { + let data = await client.account.get(); + + document.getElementById("username").value = data.username; + } + + async delete_accounts() + { + let current_password = document.getElementById("current_password").value; + + let response_data = await client.account.delete(current_password); + + if (response_data === null) + { + navigateTo("/login"); + return; + } + + ["delete", "current_password"].forEach(error_field => { + let error_display = document.getElementById(`error_${error_field}`); + if (error_display != null) + error_display.innerHTML = ""; + }); + + if (response_data === "user deleted") + { + document.getElementById(`error_delete`).innerHTML = "OK"; + navigateTo("/login") + return; + } + + document.getElementById("error_current_password").innerHTML = response_data["password"] + } + + async save() + { + let username = document.getElementById("username").value; + let new_password = document.getElementById("new_password").value; + let current_password = document.getElementById("current_password").value; + + let data = {}; + + data.username = username; + if (new_password.length != 0) + data.new_password = new_password; + let response_data = await client.account.update(data, current_password); + + if (response_data === null) + { + navigateTo(super.redirect_url); + return; + } + + ["username", "new_password", "current_password"].forEach(error_field => { + let error_display = document.getElementById(`error_${error_field}`); + if (error_display != null) + error_display.innerHTML = ""; + }); + + Object.keys(response_data).forEach(error_field => { + let error_display = document.getElementById(`error_${error_field}`); + if (error_display != null) + error_display.innerHTML = response_data[error_field]; + }); + } + + async getHtml() + { + return ` +

ME

+ + + + + + + + + + + Logout + `; + } +} \ No newline at end of file diff --git a/frontend/static/js/views/accounts/RegisterView.js b/frontend/static/js/views/accounts/RegisterView.js index 5873b4c..444149f 100644 --- a/frontend/static/js/views/accounts/RegisterView.js +++ b/frontend/static/js/views/accounts/RegisterView.js @@ -6,8 +6,14 @@ async function register() let username = document.getElementById("username").value; let password = document.getElementById("password").value; - let response = await client.accounts.create(username, password); - let response_data = await response.json(); + let response_data = await client.account.create(username, password); + + if (response_data == null) + { + navigateTo("/home"); + return; + } + ["username", "user", "password"].forEach(error_field => { let error_display = document.getElementById(`error_${error_field}`); diff --git a/trancendence/settings.py b/trancendence/settings.py index 2c57c6a..e6f29dd 100644 --- a/trancendence/settings.py +++ b/trancendence/settings.py @@ -25,12 +25,12 @@ SECRET_KEY = 'django-insecure-18!@88-wm-!skec9^n-85n(f$my^#mh3!#@f=_e@=*arh_yyjj # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True -CSRF_TRUSTED_ORIGINS = ['https://code.chauvet.pro', 'https://django.chauvet.pro'] - ALLOWED_HOSTS = ["*"] CORS_ORIGIN_ALLOW_ALL = False +CSRF_TRUSTED_ORIGINS = ["https://django.chauvet.pro"] + CORS_ORIGIN_WHITELIST = ( 'http://localhost:8000', )