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");
|
||||
}
|
||||
|
||||
if (ApplicationState.DbContext!.Users.FirstOrDefault(user => user.Username == input["username"]!.ToString()) != null) {
|
||||
return BadRequest("User already exists");
|
||||
}
|
||||
|
||||
// Hash password
|
||||
var passwordHasher = new PasswordHasher<object>();
|
||||
string hashedPassword = passwordHasher.HashPassword(null, input["password"]!.ToString());
|
||||
@ -81,4 +85,49 @@ public class UserController : ControllerBase
|
||||
|
||||
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",
|
||||
"version": "0.0.0",
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.5"
|
||||
"vue-router": "^4.2.5",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
@ -384,6 +386,11 @@
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
|
||||
"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": {
|
||||
"version": "4.5.0",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||
@ -737,6 +827,17 @@
|
||||
"peerDependencies": {
|
||||
"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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^10.7.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-router": "^4.2.5"
|
||||
"vue-router": "^4.2.5",
|
||||
"vuex": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vitejs/plugin-vue": "^4.4.0",
|
||||
|
@ -1,23 +1,49 @@
|
||||
<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 xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", import.meta.env.VITE_API_URL + "/dispense");
|
||||
xhr.send();
|
||||
const router = useRouter();
|
||||
const bus = useEventBus("login");
|
||||
const userStore = useStore("userStore")
|
||||
|
||||
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>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
|
||||
<nav>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/about">About</RouterLink>
|
||||
<nav>
|
||||
<RouterLink to="/">Home</RouterLink>
|
||||
<RouterLink to="/about">About</RouterLink>
|
||||
|
||||
<template v-if="!loggedIn">
|
||||
<RouterLink to="/login">Login</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 />
|
||||
</header>
|
||||
|
@ -1,17 +1,26 @@
|
||||
// Send request to backend API
|
||||
function request(method, path, data = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
xhr.onload = () => {
|
||||
if (xhr.status >= 200 && xhr.status < 300)
|
||||
resolve(xhr.responseText ? JSON.parse(xhr.responseText) : null);
|
||||
else
|
||||
reject(xhr.responseText || "HTTP " + xhr.status);
|
||||
}
|
||||
|
||||
xhr.onerror = () => reject("Something went wrong");
|
||||
|
||||
xhr.withCredentials = true;
|
||||
xhr.open(method, import.meta.env.VITE_API_URL + path);
|
||||
if (data) xhr.setRequestHeader("Content-Type", "application/json"),
|
||||
xhr.send(JSON.stringify(data));
|
||||
|
||||
if (data) {
|
||||
xhr.setRequestHeader("Content-Type", "application/json");
|
||||
xhr.send(JSON.stringify(data));
|
||||
} else {
|
||||
xhr.send();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
body, h1{
|
||||
body, h1, h2, h3, h4, h5, h6 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@ -30,12 +30,13 @@ input {
|
||||
nav {
|
||||
padding: 5px;
|
||||
}
|
||||
nav a {
|
||||
nav > * {
|
||||
padding: 5px 10px;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
transition-duration: 200ms;
|
||||
border: none;
|
||||
}
|
||||
nav a:not(.router-link-active):hover {
|
||||
background-color: rgba(0, 0, 0, 0.61);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import './assets/main.css';
|
||||
import userStore from "./stores/userStore";
|
||||
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
@ -7,6 +8,7 @@ import router from './router';
|
||||
const app = createApp(App);
|
||||
|
||||
app.use(router);
|
||||
app.use(userStore, "userStore");
|
||||
|
||||
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>
|
||||
import { request } from "../assets/helpers.js";
|
||||
import { useStore } from "vuex";
|
||||
|
||||
const userStore = useStore("userStore");
|
||||
|
||||
async function dispense() {
|
||||
await request("POST", "/dispense");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<main>
|
||||
<h1>M&M Dispenser</h1>
|
||||
<template v-if="userStore.state.userInfo">
|
||||
<h2>Welcome back, {{ userStore.state.userInfo.username }}</h2>
|
||||
<br>
|
||||
<button @click="dispense">Dispense the m&m</button>
|
||||
</template>
|
||||
|
||||
<button @click="dispense">Dispense the m&m</button>
|
||||
<template v-else>
|
||||
<h1>M&M Dispenser</h1>
|
||||
|
||||
<p>Please login to continue</p>
|
||||
</template>
|
||||
</main>
|
||||
</template>
|
||||
|
||||
|
@ -1,6 +1,11 @@
|
||||
<script setup>
|
||||
import { request } from "../assets/helpers.js";
|
||||
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 password = ref(null);
|
||||
@ -9,12 +14,13 @@ const error = ref("");
|
||||
|
||||
function login() {
|
||||
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() {
|
||||
alert("Successfully logged in");
|
||||
bus.emit();
|
||||
router.push("/");
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -1,6 +1,9 @@
|
||||
<script setup>
|
||||
import { request } from "../assets/helpers.js";
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const username = ref(null);
|
||||
const password = ref(null);
|
||||
@ -14,7 +17,7 @@ function register() {
|
||||
}
|
||||
|
||||
function success() {
|
||||
alert("Successfully registered an account");
|
||||
router.push("/login");
|
||||
}
|
||||
</script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user