Add homepage for logged in users, logout button, retrieving user info from backend
This commit is contained in:
parent
7c3bd135d1
commit
839cbe5463
@ -18,6 +18,10 @@ public class UserController : ControllerBase
|
|||||||
return BadRequest("Username and password required");
|
return BadRequest("Username and password required");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ApplicationState.DbContext!.Users.FirstOrDefault(user => user.Username == input["username"]!.ToString()) != null) {
|
||||||
|
return BadRequest("User already exists");
|
||||||
|
}
|
||||||
|
|
||||||
// Hash password
|
// Hash password
|
||||||
var passwordHasher = new PasswordHasher<object>();
|
var passwordHasher = new PasswordHasher<object>();
|
||||||
string hashedPassword = passwordHasher.HashPassword(null, input["password"]!.ToString());
|
string hashedPassword = passwordHasher.HashPassword(null, input["password"]!.ToString());
|
||||||
@ -81,4 +85,49 @@ public class UserController : ControllerBase
|
|||||||
|
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost("Logout")]
|
||||||
|
public IActionResult LogOut()
|
||||||
|
{
|
||||||
|
// Validate
|
||||||
|
if (Request.Cookies["session"] == null) {
|
||||||
|
return BadRequest("You are not logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user
|
||||||
|
var user = ApplicationState.DbContext!.Users.FirstOrDefault(user => user.SessionToken == Request.Cookies["session"]!.ToString());
|
||||||
|
if (user == null) {
|
||||||
|
return BadRequest("Invalid session token");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log out
|
||||||
|
user.SessionToken = null;
|
||||||
|
ApplicationState.DbContext!.SaveChanges();
|
||||||
|
|
||||||
|
Response.Cookies.Delete("session");
|
||||||
|
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("UserInfo")]
|
||||||
|
public IActionResult UserInfo()
|
||||||
|
{
|
||||||
|
// Validate
|
||||||
|
if (Request.Cookies["session"] == null) {
|
||||||
|
return BadRequest("You are not logged in");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get user
|
||||||
|
var user = ApplicationState.DbContext!.Users.FirstOrDefault(user => user.SessionToken == Request.Cookies["session"]!.ToString());
|
||||||
|
if (user == null) {
|
||||||
|
return BadRequest("Invalid session token");
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = new {
|
||||||
|
username = user.Username,
|
||||||
|
touchCode = user.TouchCode,
|
||||||
|
};
|
||||||
|
|
||||||
|
return new JsonResult(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
103
frontend/package-lock.json
generated
103
frontend/package-lock.json
generated
@ -8,8 +8,10 @@
|
|||||||
"name": "slik-dispenser",
|
"name": "slik-dispenser",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vueuse/core": "^10.7.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5",
|
||||||
|
"vuex": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.4.0",
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
@ -384,6 +386,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||||
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/web-bluetooth": {
|
||||||
|
"version": "0.0.20",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
|
||||||
|
"integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow=="
|
||||||
|
},
|
||||||
"node_modules/@vitejs/plugin-vue": {
|
"node_modules/@vitejs/plugin-vue": {
|
||||||
"version": "4.5.0",
|
"version": "4.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.0.tgz",
|
||||||
@ -504,6 +511,89 @@
|
|||||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.9.tgz",
|
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.9.tgz",
|
||||||
"integrity": "sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA=="
|
"integrity": "sha512-ZE0VTIR0LmYgeyhurPTpy4KzKsuDyQbMSdM49eKkMnT5X4VfFBLysMzjIZhLEFQYjjOVVfbvUDHckwjDFiO2eA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@vueuse/core": {
|
||||||
|
"version": "10.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.7.0.tgz",
|
||||||
|
"integrity": "sha512-4EUDESCHtwu44ZWK3Gc/hZUVhVo/ysvdtwocB5vcauSV4B7NiGY5972WnsojB3vRNdxvAt7kzJWE2h9h7C9d5w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/web-bluetooth": "^0.0.20",
|
||||||
|
"@vueuse/metadata": "10.7.0",
|
||||||
|
"@vueuse/shared": "10.7.0",
|
||||||
|
"vue-demi": ">=0.14.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/core/node_modules/vue-demi": {
|
||||||
|
"version": "0.14.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||||
|
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/metadata": {
|
||||||
|
"version": "10.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.7.0.tgz",
|
||||||
|
"integrity": "sha512-GlaH7tKP2iBCZ3bHNZ6b0cl9g0CJK8lttkBNUX156gWvNYhTKEtbweWLm9rxCPIiwzYcr/5xML6T8ZUEt+DkvA==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/shared": {
|
||||||
|
"version": "10.7.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.7.0.tgz",
|
||||||
|
"integrity": "sha512-kc00uV6CiaTdc3i1CDC4a3lBxzaBE9AgYNtFN87B5OOscqeWElj/uza8qVDmk7/U8JbqoONLbtqiLJ5LGRuqlw==",
|
||||||
|
"dependencies": {
|
||||||
|
"vue-demi": ">=0.14.6"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@vueuse/shared/node_modules/vue-demi": {
|
||||||
|
"version": "0.14.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz",
|
||||||
|
"integrity": "sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==",
|
||||||
|
"hasInstallScript": true,
|
||||||
|
"bin": {
|
||||||
|
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||||
|
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/antfu"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@vue/composition-api": "^1.0.0-rc.1",
|
||||||
|
"vue": "^3.0.0-0 || ^2.6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@vue/composition-api": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/csstype": {
|
"node_modules/csstype": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||||
@ -737,6 +827,17 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"vue": "^3.2.0"
|
"vue": "^3.2.0"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"node_modules/vuex": {
|
||||||
|
"version": "4.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz",
|
||||||
|
"integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==",
|
||||||
|
"dependencies": {
|
||||||
|
"@vue/devtools-api": "^6.0.0-beta.11"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"vue": "^3.2.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,10 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@vueuse/core": "^10.7.0",
|
||||||
"vue": "^3.3.4",
|
"vue": "^3.3.4",
|
||||||
"vue-router": "^4.2.5"
|
"vue-router": "^4.2.5",
|
||||||
|
"vuex": "^4.1.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^4.4.0",
|
"@vitejs/plugin-vue": "^4.4.0",
|
||||||
|
@ -1,23 +1,49 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { RouterLink, RouterView } from 'vue-router';
|
import { RouterLink, RouterView, useRouter } from "vue-router";
|
||||||
|
import { request } from "./assets/helpers.js";
|
||||||
|
import { ref, watch, computed } from "vue";
|
||||||
|
import { useEventBus } from "@vueuse/core";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
function dispense() {
|
const router = useRouter();
|
||||||
const xhr = new XMLHttpRequest();
|
const bus = useEventBus("login");
|
||||||
xhr.open("POST", import.meta.env.VITE_API_URL + "/dispense");
|
const userStore = useStore("userStore")
|
||||||
xhr.send();
|
|
||||||
|
let loggedIn = ref(!!document.cookie.match(/session=/));
|
||||||
|
if (loggedIn.value) getUserInfo();
|
||||||
|
|
||||||
|
bus.on(() => {
|
||||||
|
loggedIn.value = true
|
||||||
|
getUserInfo();
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getUserInfo() {
|
||||||
|
userStore.dispatch("updateUserInfo");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout() {
|
||||||
|
await request("POST", "/logout");
|
||||||
|
loggedIn.value = false;
|
||||||
|
router.push("/");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<header>
|
<header>
|
||||||
|
|
||||||
<nav>
|
<nav>
|
||||||
<RouterLink to="/">Home</RouterLink>
|
<RouterLink to="/">Home</RouterLink>
|
||||||
<RouterLink to="/about">About</RouterLink>
|
<RouterLink to="/about">About</RouterLink>
|
||||||
|
|
||||||
|
<template v-if="!loggedIn">
|
||||||
<RouterLink to="/login">Login</RouterLink>
|
<RouterLink to="/login">Login</RouterLink>
|
||||||
<RouterLink to="/register">Register</RouterLink>
|
<RouterLink to="/register">Register</RouterLink>
|
||||||
</nav>
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
|
<span style="color: #BDBDBD;">{{ userStore.state.userInfo?.username ?? "Loading.." }}</span>
|
||||||
|
<a href="#" @click.prevent="logout">Log out</a>
|
||||||
|
</template>
|
||||||
|
</nav>
|
||||||
|
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</header>
|
</header>
|
||||||
|
@ -1,17 +1,26 @@
|
|||||||
|
// Send request to backend API
|
||||||
function request(method, path, data = null) {
|
function request(method, path, data = null) {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
|
|
||||||
xhr.onload = () => {
|
xhr.onload = () => {
|
||||||
if (xhr.status >= 200 && xhr.status < 300)
|
if (xhr.status >= 200 && xhr.status < 300)
|
||||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||||
else
|
else
|
||||||
reject(xhr.responseText || "HTTP " + xhr.status);
|
reject(xhr.responseText || "HTTP " + xhr.status);
|
||||||
}
|
}
|
||||||
|
|
||||||
xhr.onerror = () => reject("Something went wrong");
|
xhr.onerror = () => reject("Something went wrong");
|
||||||
|
|
||||||
xhr.withCredentials = true;
|
xhr.withCredentials = true;
|
||||||
xhr.open(method, import.meta.env.VITE_API_URL + path);
|
xhr.open(method, import.meta.env.VITE_API_URL + path);
|
||||||
if (data) xhr.setRequestHeader("Content-Type", "application/json"),
|
|
||||||
|
if (data) {
|
||||||
|
xhr.setRequestHeader("Content-Type", "application/json");
|
||||||
xhr.send(JSON.stringify(data));
|
xhr.send(JSON.stringify(data));
|
||||||
|
} else {
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
body, h1{
|
body, h1, h2, h3, h4, h5, h6 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,12 +30,13 @@ input {
|
|||||||
nav {
|
nav {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
}
|
}
|
||||||
nav a {
|
nav > * {
|
||||||
padding: 5px 10px;
|
padding: 5px 10px;
|
||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
color: white;
|
color: white;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
transition-duration: 200ms;
|
transition-duration: 200ms;
|
||||||
|
border: none;
|
||||||
}
|
}
|
||||||
nav a:not(.router-link-active):hover {
|
nav a:not(.router-link-active):hover {
|
||||||
background-color: rgba(0, 0, 0, 0.61);
|
background-color: rgba(0, 0, 0, 0.61);
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import './assets/main.css';
|
import './assets/main.css';
|
||||||
|
import userStore from "./stores/userStore";
|
||||||
|
|
||||||
import { createApp } from 'vue';
|
import { createApp } from 'vue';
|
||||||
import App from './App.vue';
|
import App from './App.vue';
|
||||||
@ -7,6 +8,7 @@ import router from './router';
|
|||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
|
||||||
app.use(router);
|
app.use(router);
|
||||||
|
app.use(userStore, "userStore");
|
||||||
|
|
||||||
app.mount('#app');
|
app.mount('#app');
|
||||||
|
|
||||||
|
19
frontend/src/stores/userStore.js
Normal file
19
frontend/src/stores/userStore.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { createStore } from "vuex";
|
||||||
|
import { request } from "../assets/helpers.js";
|
||||||
|
|
||||||
|
export default createStore({
|
||||||
|
state: {
|
||||||
|
userInfo: null,
|
||||||
|
},
|
||||||
|
mutations: {
|
||||||
|
setUserInfo(state, userInfo) {
|
||||||
|
state.userInfo = userInfo;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
async updateUserInfo({ commit }) {
|
||||||
|
const userInfo = await request("GET", "/userinfo");
|
||||||
|
commit("setUserInfo", userInfo);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
});
|
@ -1,11 +1,27 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
|
import { request } from "../assets/helpers.js";
|
||||||
|
import { useStore } from "vuex";
|
||||||
|
|
||||||
|
const userStore = useStore("userStore");
|
||||||
|
|
||||||
|
async function dispense() {
|
||||||
|
await request("POST", "/dispense");
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<main>
|
<main>
|
||||||
|
<template v-if="userStore.state.userInfo">
|
||||||
|
<h2>Welcome back, {{ userStore.state.userInfo.username }}</h2>
|
||||||
|
<br>
|
||||||
|
<button @click="dispense">Dispense the m&m</button>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else>
|
||||||
<h1>M&M Dispenser</h1>
|
<h1>M&M Dispenser</h1>
|
||||||
|
|
||||||
<button @click="dispense">Dispense the m&m</button>
|
<p>Please login to continue</p>
|
||||||
|
</template>
|
||||||
</main>
|
</main>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { request } from "../assets/helpers.js";
|
import { request } from "../assets/helpers.js";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { useEventBus } from "@vueuse/core";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const bus = useEventBus("login");
|
||||||
|
|
||||||
const username = ref(null);
|
const username = ref(null);
|
||||||
const password = ref(null);
|
const password = ref(null);
|
||||||
@ -9,12 +14,13 @@ const error = ref("");
|
|||||||
|
|
||||||
function login() {
|
function login() {
|
||||||
request("POST", "/login", { username: username.value.value, password: password.value.value })
|
request("POST", "/login", { username: username.value.value, password: password.value.value })
|
||||||
.then(success)
|
.catch(err => error.value = err)
|
||||||
.catch(err => error.value = err);
|
.then(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
function success() {
|
function success() {
|
||||||
alert("Successfully logged in");
|
bus.emit();
|
||||||
|
router.push("/");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { request } from "../assets/helpers.js";
|
import { request } from "../assets/helpers.js";
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
const username = ref(null);
|
const username = ref(null);
|
||||||
const password = ref(null);
|
const password = ref(null);
|
||||||
@ -14,7 +17,7 @@ function register() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function success() {
|
function success() {
|
||||||
alert("Successfully registered an account");
|
router.push("/login");
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user