240 lines
5.3 KiB
Vue
240 lines
5.3 KiB
Vue
<script setup>
|
|
import CreateSecret from "./components/CreateSecret.vue";
|
|
import HomePage from "./components/HomePage.vue";
|
|
import ImportSecrets from "./components/ImportSecrets.vue";
|
|
import ListSecrets from "./components/ListSecrets.vue";
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<div id="header" class="header">
|
|
<span class="logo">FastAuth</span>
|
|
<div class="header-buttons" v-if="loggedin">
|
|
<button class="header-button" title="Refresh secrets" @click="refresh">
|
|
<img src="./assets/refresh-icon.png" class="button-icon" />
|
|
</button>
|
|
<button class="header-button" title="logout" @click="logout">
|
|
<img src="./assets/logout-icon.png" class="button-icon" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<el-dialog
|
|
v-model="creationDialog"
|
|
title="Add a new TOTP secret"
|
|
destroy-on-close
|
|
width="80vw"
|
|
>
|
|
<CreateSecret @close="secretSaved" />
|
|
</el-dialog>
|
|
|
|
<el-dialog
|
|
v-model="editDialog"
|
|
title="Edit TOTP secret"
|
|
destroy-on-close
|
|
width="80vw"
|
|
>
|
|
<CreateSecret :editSecret="editingSecret" @close="secretSaved" />
|
|
</el-dialog>
|
|
|
|
<el-dialog
|
|
v-model="importDialog"
|
|
title="Import TOTP secrets"
|
|
destroy-on-close
|
|
width="80vw"
|
|
>
|
|
<ImportSecrets @close="secretsImported" />
|
|
</el-dialog>
|
|
|
|
<div class="container">
|
|
<div class="timer" v-if="loggedin" :style="{ width: timerWidth + '%' }"></div>
|
|
<HomePage
|
|
msg="You did it!"
|
|
@loggedin="
|
|
loggedin = true;
|
|
showSecrets = true;
|
|
"
|
|
v-if="!loggedin"
|
|
/>
|
|
<!-- <el-button @click="showSecrets = true" v-if="loggedin"> Show secrets </el-button>
|
|
<el-button @click="showSecrets = false" v-if="showSecrets && loggedin">
|
|
Hide secrets
|
|
</el-button> -->
|
|
<ListSecrets :key="listUpdated" v-if="showSecrets && loggedin" @edit="editSecret" />
|
|
</div>
|
|
<button class="create-floating" @click="creationDialog = true">+</button>
|
|
<button class="create-floating mb50" @click="importDialog = true">i</button>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
components: { ImportSecrets },
|
|
data() {
|
|
return {
|
|
loggedin: false,
|
|
showSecrets: false,
|
|
creationDialog: false,
|
|
listUpdated: 1,
|
|
apiBaseUrl: "http://localhost:8000",
|
|
editDialog: false,
|
|
editingSecret: {},
|
|
importDialog: false,
|
|
timerWidth: 0,
|
|
};
|
|
},
|
|
methods: {
|
|
logout() {
|
|
sessionStorage.removeItem("token");
|
|
this.loggedin = false;
|
|
},
|
|
|
|
secretSaved() {
|
|
this.creationDialog = false;
|
|
this.editDialog = false;
|
|
this.listUpdated += 1;
|
|
},
|
|
|
|
secretsImported() {
|
|
this.importDialog = false;
|
|
this.listUpdated += 1;
|
|
},
|
|
|
|
async validateToken() {
|
|
const url = `${this.apiBaseUrl}/validate-token`;
|
|
const token = sessionStorage.getItem("token");
|
|
const requestOptions = {
|
|
method: "GET",
|
|
headers: {
|
|
"Content-Type": "application/json",
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
};
|
|
|
|
const response = await fetch(url, requestOptions)
|
|
.then((response) => response.json())
|
|
.catch((err) => {
|
|
console.log(err);
|
|
return false;
|
|
});
|
|
|
|
if (!response) {
|
|
return false;
|
|
}
|
|
if ("message" in response) {
|
|
if (response["message"] === "authenticated") {
|
|
console.log("token validated");
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
},
|
|
|
|
editSecret(secret) {
|
|
this.editingSecret = secret;
|
|
// console.log(this.editingSecret);
|
|
this.editDialog = true;
|
|
},
|
|
|
|
refresh() {
|
|
this.listUpdated += 1;
|
|
},
|
|
|
|
startTimer() {
|
|
// console.log("start timer called");
|
|
this.interval = setInterval(() => {
|
|
const now = new Date();
|
|
const seconds = now.getSeconds();
|
|
const remainingTime = (seconds > 30 ? 60 : 30) - seconds;
|
|
this.timerWidth = (remainingTime / 30) * 100;
|
|
}, 1000);
|
|
},
|
|
},
|
|
|
|
async mounted() {
|
|
if ("token" in sessionStorage) {
|
|
const tokenValid = await this.validateToken();
|
|
|
|
if (tokenValid) {
|
|
this.loggedin = true;
|
|
this.showSecrets = true;
|
|
}
|
|
this.startTimer();
|
|
}
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style scoped>
|
|
.container {
|
|
margin-top: 6vh;
|
|
}
|
|
|
|
.logoutBtn {
|
|
/* position: relative; */
|
|
margin-right: 0;
|
|
margin-left: auto;
|
|
}
|
|
.el-page-header__back {
|
|
display: none !important;
|
|
}
|
|
|
|
.header {
|
|
width: 100vw;
|
|
height: 48px;
|
|
position: fixed;
|
|
left: 0;
|
|
top: 0;
|
|
background-color: rgb(170, 247, 247);
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 12px 0 12px;
|
|
justify-content: space-between;
|
|
box-shadow: 2px 0px 8px #aaa;
|
|
z-index: 999;
|
|
}
|
|
.logo {
|
|
font-size: 1.3rem;
|
|
font-weight: 700;
|
|
margin-left: 2vw;
|
|
}
|
|
|
|
.timer {
|
|
position: fixed;
|
|
bottom: 4px;
|
|
height: 0.3rem;
|
|
background-color: green;
|
|
}
|
|
|
|
.create-floating {
|
|
position: fixed;
|
|
bottom: 4vh;
|
|
right: 4vw;
|
|
height: 40px;
|
|
width: 40px;
|
|
border: none;
|
|
border-radius: 20px;
|
|
box-shadow: 4px 4px 6px #999;
|
|
background-color: teal;
|
|
color: white;
|
|
font-size: 1.6rem;
|
|
cursor: pointer;
|
|
}
|
|
|
|
.button-icon {
|
|
width: 24px;
|
|
margin-top: 8px;
|
|
height: 24px;
|
|
opacity: 0.6;
|
|
}
|
|
|
|
.header-button {
|
|
border: none;
|
|
background: none;
|
|
}
|
|
|
|
.mb50 {
|
|
margin-bottom: 50px;
|
|
}
|
|
</style>
|