Compare commits
12 Commits
334bb97d35
...
32fb68c5ea
Author | SHA1 | Date | |
---|---|---|---|
32fb68c5ea | |||
ba400fd5f6 | |||
72583eb2ec | |||
5dd375b796 | |||
649cadd591 | |||
64b5708046 | |||
8b9b903ad5 | |||
4727837919 | |||
52a3f852fe | |||
08a4882eaa | |||
ee31d128d5 | |||
3f1ddacce3 |
@ -6,27 +6,28 @@ import ListSecrets from "./components/ListSecrets.vue";
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div id="header">
|
<div id="header" class="header">
|
||||||
<span class="logo">FastAuth</span>
|
<span class="logo">FastAuth</span>
|
||||||
<div class="header-buttons" v-if="loggedin">
|
<div class="header-buttons" v-if="loggedin">
|
||||||
<el-button type="primary" class="ml-2" @click="creationDialog = true">
|
<button class="header-button" title="Refresh secrets" @click="refresh">
|
||||||
Create
|
<img src="./assets/refresh-icon.png" class="button-icon" />
|
||||||
</el-button>
|
</button>
|
||||||
<el-button type="warning" class="ml-2" @click="logout">Logout</el-button>
|
<button class="header-button" title="logout" @click="logout">
|
||||||
<el-button type="error" @click="refresh">Refresh</el-button>
|
<img src="./assets/logout-icon.png" class="button-icon" />
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="timer" :style="{ width: timerWidth + '%' }"></div>
|
|
||||||
|
|
||||||
<el-dialog v-model="creationDialog" title="Add a new TOTP secret" width="80vw">
|
<el-dialog v-model="creationDialog" title="Add a new TOTP secret" width="80vw">
|
||||||
<CreateSecret @close="creationDialog = false" />
|
<CreateSecret @close="secretSaved" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<el-dialog v-model="editDialog" title="Edit TOTP secret" width="80vw">
|
<el-dialog v-model="editDialog" title="Edit TOTP secret" width="80vw">
|
||||||
<CreateSecret :editSecret="editingSecret" @close="editDialog = false" />
|
<CreateSecret :editSecret="editingSecret" @close="secretSaved" />
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
|
<div class="timer" v-if="loggedin" :style="{ width: timerWidth + '%' }"></div>
|
||||||
<HomePage
|
<HomePage
|
||||||
msg="You did it!"
|
msg="You did it!"
|
||||||
@loggedin="
|
@loggedin="
|
||||||
@ -41,6 +42,7 @@ import ListSecrets from "./components/ListSecrets.vue";
|
|||||||
</el-button> -->
|
</el-button> -->
|
||||||
<ListSecrets :key="listUpdated" v-if="showSecrets && loggedin" @edit="editSecret" />
|
<ListSecrets :key="listUpdated" v-if="showSecrets && loggedin" @edit="editSecret" />
|
||||||
</div>
|
</div>
|
||||||
|
<button class="create-floating" @click="creationDialog = true">+</button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -55,7 +57,7 @@ export default {
|
|||||||
apiBaseUrl: "http://localhost:8000",
|
apiBaseUrl: "http://localhost:8000",
|
||||||
editDialog: false,
|
editDialog: false,
|
||||||
editingSecret: {},
|
editingSecret: {},
|
||||||
timerWidth: 100,
|
timerWidth: 0,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -66,9 +68,8 @@ export default {
|
|||||||
|
|
||||||
secretSaved() {
|
secretSaved() {
|
||||||
this.creationDialog = false;
|
this.creationDialog = false;
|
||||||
console.log("before update", this.listUpdated);
|
this.editDialog = false;
|
||||||
this.listUpdated += 1;
|
this.listUpdated += 1;
|
||||||
console.log("after update", this.listUpdated);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async validateToken() {
|
async validateToken() {
|
||||||
@ -112,15 +113,12 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
startTimer() {
|
startTimer() {
|
||||||
|
// console.log("start timer called");
|
||||||
this.interval = setInterval(() => {
|
this.interval = setInterval(() => {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const seconds = now.getSeconds();
|
const seconds = now.getSeconds();
|
||||||
const remainingTime = (seconds > 30 ? 60 : 30) - seconds;
|
const remainingTime = (seconds > 30 ? 60 : 30) - seconds;
|
||||||
// console.log(remainingTime);
|
|
||||||
this.timerWidth = (remainingTime / 30) * 100;
|
this.timerWidth = (remainingTime / 30) * 100;
|
||||||
if (remainingTime === 30) {
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
}, 1000);
|
}, 1000);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -139,19 +137,9 @@ export default {
|
|||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style scoped>
|
||||||
.header {
|
|
||||||
position: absolute;
|
|
||||||
width: 100vw;
|
|
||||||
height: 3rem;
|
|
||||||
padding: 0.3rem;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
background-color: aquamarine;
|
|
||||||
}
|
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
margin-top: 0;
|
margin-top: 6vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.logoutBtn {
|
.logoutBtn {
|
||||||
@ -163,10 +151,10 @@ export default {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
#header {
|
.header {
|
||||||
width: 100vw;
|
width: 100vw;
|
||||||
height: 3rem;
|
height: 48px;
|
||||||
position: absolute;
|
position: fixed;
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
background-color: rgb(170, 247, 247);
|
background-color: rgb(170, 247, 247);
|
||||||
@ -175,15 +163,45 @@ export default {
|
|||||||
padding: 0 12px 0 12px;
|
padding: 0 12px 0 12px;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
box-shadow: 2px 0px 8px #aaa;
|
box-shadow: 2px 0px 8px #aaa;
|
||||||
|
z-index: 999;
|
||||||
}
|
}
|
||||||
.logo {
|
.logo {
|
||||||
font-size: 1.3rem;
|
font-size: 1.3rem;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
margin-left: 2vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.timer {
|
.timer {
|
||||||
margin-top: 1.2rem;
|
position: fixed;
|
||||||
|
bottom: 4px;
|
||||||
height: 0.3rem;
|
height: 0.3rem;
|
||||||
background-color: green;
|
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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -65,7 +65,7 @@ body {
|
|||||||
transition:
|
transition:
|
||||||
color 0.5s,
|
color 0.5s,
|
||||||
background-color 0.5s; */
|
background-color 0.5s; */
|
||||||
line-height: 1.6;
|
line-height: 1.2;
|
||||||
font-family:
|
font-family:
|
||||||
/* Inter,
|
/* Inter,
|
||||||
-apple-system,
|
-apple-system,
|
||||||
|
BIN
frontend/src/assets/logout-icon.png
Normal file
BIN
frontend/src/assets/logout-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -1,10 +1,11 @@
|
|||||||
@import './base.css';
|
@import './base.css';
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
max-width: 1280px;
|
/* max-width: 1280px; */
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding: 2rem;
|
padding: 1rem;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
a,
|
a,
|
||||||
@ -24,12 +25,13 @@ a,
|
|||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
body {
|
body {
|
||||||
display: flex;
|
display: flex;
|
||||||
place-items: center;
|
/* place-items: center; */
|
||||||
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
display: grid;
|
/* display: grid; */
|
||||||
grid-template-columns: 1fr 1fr;
|
/* grid-template-columns: 1fr 1fr; */
|
||||||
padding: 0 2rem;
|
padding: 0 2rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
BIN
frontend/src/assets/refresh-icon.png
Normal file
BIN
frontend/src/assets/refresh-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 18 KiB |
@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div>
|
<div>
|
||||||
<br />
|
|
||||||
<div class="filter-buttons">
|
<div class="filter-buttons">
|
||||||
<el-button-group>
|
<el-button-group>
|
||||||
<el-button
|
<el-button
|
||||||
@ -16,6 +15,30 @@
|
|||||||
>Clear</el-button
|
>Clear</el-button
|
||||||
>
|
>
|
||||||
</el-button-group>
|
</el-button-group>
|
||||||
|
<div>
|
||||||
|
<el-form-item label="Show OTPs">
|
||||||
|
<el-switch v-model="showSecrets" active-value="yes" inactive-value="no" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="sort-options">
|
||||||
|
<span>
|
||||||
|
<el-form-item label="Sort">
|
||||||
|
<el-select
|
||||||
|
v-model="currentSort"
|
||||||
|
placeholder="Select"
|
||||||
|
style="width: 80px"
|
||||||
|
@change="sortItems"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in sortOptions"
|
||||||
|
:key="item.key"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.key"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -26,6 +49,8 @@
|
|||||||
:username="secret.username"
|
:username="secret.username"
|
||||||
:secret="secret.secret"
|
:secret="secret.secret"
|
||||||
:id="secret.id"
|
:id="secret.id"
|
||||||
|
:showOtp="showSecrets"
|
||||||
|
:key="showSecrets"
|
||||||
@edit="editSecret"
|
@edit="editSecret"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@ -57,6 +82,18 @@ export default {
|
|||||||
currentFilter: [],
|
currentFilter: [],
|
||||||
loadCards: false,
|
loadCards: false,
|
||||||
showClear: false,
|
showClear: false,
|
||||||
|
showSecrets: "yes",
|
||||||
|
currentSort: "asc",
|
||||||
|
sortOptions: [
|
||||||
|
{
|
||||||
|
name: "A-Z",
|
||||||
|
key: "asc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Z-A",
|
||||||
|
key: "desc",
|
||||||
|
},
|
||||||
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -87,6 +124,7 @@ export default {
|
|||||||
this.secretsList.push(row);
|
this.secretsList.push(row);
|
||||||
});
|
});
|
||||||
this.filteredSecretsList = this.secretsList;
|
this.filteredSecretsList = this.secretsList;
|
||||||
|
this.sortItems();
|
||||||
this.loadCards = true;
|
this.loadCards = true;
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -129,6 +167,14 @@ export default {
|
|||||||
this.filteredSecretsList = this.secretsList;
|
this.filteredSecretsList = this.secretsList;
|
||||||
this.showClear = false;
|
this.showClear = false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
sortItems() {
|
||||||
|
if (this.currentSort === "asc") {
|
||||||
|
this.filteredSecretsList.sort((a, b) => a.issuer.localeCompare(b.issuer));
|
||||||
|
} else if (this.currentSort === "desc") {
|
||||||
|
this.filteredSecretsList.sort((a, b) => b.issuer.localeCompare(a.issuer));
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
computed: {},
|
computed: {},
|
||||||
@ -137,11 +183,28 @@ export default {
|
|||||||
currentFilter: function () {
|
currentFilter: function () {
|
||||||
const filteredList = this.secretsList.filter(this.checkFirstLetter);
|
const filteredList = this.secretsList.filter(this.checkFirstLetter);
|
||||||
this.filteredSecretsList = filteredList;
|
this.filteredSecretsList = filteredList;
|
||||||
|
this.sortItems();
|
||||||
|
},
|
||||||
|
|
||||||
|
currentSort: function () {
|
||||||
|
localStorage.setItem("currentSort", this.currentSort);
|
||||||
|
},
|
||||||
|
|
||||||
|
showSecrets: function () {
|
||||||
|
localStorage.setItem("showSecrets", this.showSecrets);
|
||||||
|
},
|
||||||
|
|
||||||
|
secretsList: function () {
|
||||||
|
this.currentSort = localStorage.getItem("currentSort");
|
||||||
|
this.filteredSecretsList = this.secretsList;
|
||||||
|
this.sortItems();
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.listSecrets();
|
this.listSecrets();
|
||||||
|
this.showSecrets = localStorage.getItem("showSecrets");
|
||||||
|
this.currentSort = localStorage.getItem("currentSort");
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -151,13 +214,21 @@ export default {
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
max-width: 90vw;
|
justify-content: space-evenly;
|
||||||
|
max-width: 96vw;
|
||||||
}
|
}
|
||||||
|
|
||||||
.filter-buttons {
|
.filter-buttons {
|
||||||
width: 90%;
|
width: 96vw;
|
||||||
display: flex;
|
display: flex;
|
||||||
margin-bottom: 20px;
|
margin: 40px 0 20px 0;
|
||||||
justify-content: center;
|
justify-content: space-around;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sort-options {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
min-width: 80px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="wrapper">
|
<div
|
||||||
|
class="wrapper"
|
||||||
|
@mouseenter="[(otpShown = true), (iconOpacity = 1)]"
|
||||||
|
@mouseleave="
|
||||||
|
[(otpShown = showOtp === 'yes' ? true : tapped ? true : false), (iconOpacity = 0.1)]
|
||||||
|
"
|
||||||
|
>
|
||||||
<div class="card-top">
|
<div class="card-top">
|
||||||
<span class="issuer">{{ issuer }}</span>
|
<span class="issuer">{{ issuer }}</span>
|
||||||
<button class="edit-button" @click="editSecret">
|
<button class="card-button" @click="editSecret">
|
||||||
<img src="../assets/edit-icon.png" class="button-icon" /></button
|
<img src="../assets/edit-icon.png" class="button-icon" /></button
|
||||||
><br />
|
><br />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<span class="user">{{ username }}</span> <br />
|
<span class="user">{{ username }}</span> <br />
|
||||||
<span
|
<div class="otp-container">
|
||||||
class="otp"
|
<span class="otp" @click="[tapReveal($event), copyOtp($event)]">
|
||||||
@click="copyOtp"
|
<span v-if="otpShown">{{ this.otp }}</span>
|
||||||
@mouseenter="otpHidden = false"
|
<span v-else> •••••• </span>
|
||||||
@mouseleave="otpHidden = true"
|
</span>
|
||||||
>
|
<button class="card-button" @click="copyOtp">
|
||||||
<span v-if="otpHidden">******</span>
|
<img src="../assets/copy-icon.png" class="button-icon" />
|
||||||
<span v-else>{{ generateTotp(secret) }}</span>
|
</button>
|
||||||
</span>
|
</div>
|
||||||
<button class="edit-button" @click="copyOtp">
|
|
||||||
<img src="../assets/copy-icon.png" class="button-icon" />
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -31,24 +34,29 @@ export default {
|
|||||||
issuer: String,
|
issuer: String,
|
||||||
username: String,
|
username: String,
|
||||||
secret: String,
|
secret: String,
|
||||||
|
showOtp: {
|
||||||
|
type: String,
|
||||||
|
default: "no",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
otp: 123456,
|
otp: 123456,
|
||||||
notes: "",
|
notes: "",
|
||||||
otpHidden: true,
|
otpShown: false,
|
||||||
|
iconOpacity: 0.1,
|
||||||
|
tapped: false,
|
||||||
|
revealTime: 10,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
generateTotp(secret) {
|
generateTotp() {
|
||||||
const { otp, expires } = TOTP.generate(secret);
|
const { otp, expires } = TOTP.generate(this.secret);
|
||||||
const remaining = (expires - Date.now()) / 30000;
|
const now = new Date();
|
||||||
this.remainingTime = remaining / 1000;
|
const remainingTime = expires - now;
|
||||||
this.timerWidth = remaining * 100;
|
|
||||||
// console.log("hello");
|
|
||||||
this.otp = otp;
|
this.otp = otp;
|
||||||
return otp;
|
setTimeout(this.generateTotp, remainingTime);
|
||||||
},
|
},
|
||||||
|
|
||||||
async copyOtp() {
|
async copyOtp() {
|
||||||
@ -62,18 +70,33 @@ export default {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async tapReveal() {
|
||||||
|
console.log("tapped");
|
||||||
|
this.otpShown = true;
|
||||||
|
this.tapped = true;
|
||||||
|
setTimeout(() => {
|
||||||
|
this.otpShown = this.showOtp === "yes" ? true : false;
|
||||||
|
this.tapped = false;
|
||||||
|
}, this.revealTime * 1000);
|
||||||
|
},
|
||||||
|
|
||||||
editSecret() {
|
editSecret() {
|
||||||
this.$emit("edit", this.id);
|
this.$emit("edit", this.id);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {},
|
mounted() {
|
||||||
|
this.otpShown = this.showOtp === "yes" ? true : false;
|
||||||
|
this.generateTotp();
|
||||||
|
},
|
||||||
|
|
||||||
|
created() {},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.wrapper {
|
.wrapper {
|
||||||
height: 110px;
|
height: 90px;
|
||||||
width: 160px;
|
width: 160px;
|
||||||
/* border: 1px solid black; */
|
/* border: 1px solid black; */
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
@ -92,22 +115,30 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.otp {
|
.otp {
|
||||||
font-size: 2rem;
|
margin-top: 0.4rem;
|
||||||
|
font-size: 1.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.card-top {
|
.card-top {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
.edit-button {
|
.card-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
right: 0;
|
right: 0;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
border: none;
|
border: none;
|
||||||
|
background: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.button-icon {
|
.button-icon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
|
opacity: v-bind("iconOpacity");
|
||||||
|
}
|
||||||
|
|
||||||
|
.otp-container {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
6
main.py
6
main.py
@ -131,7 +131,11 @@ async def create_secret(secret: Secret, current_user: dict = Depends(get_current
|
|||||||
text = f.read()
|
text = f.read()
|
||||||
if text:
|
if text:
|
||||||
data.extend(json.loads(text))
|
data.extend(json.loads(text))
|
||||||
secret_id = max(i['id'] for i in data) + 1
|
|
||||||
|
if data:
|
||||||
|
secret_id = max(i['id'] for i in data) + 1
|
||||||
|
else:
|
||||||
|
secret_id = 0
|
||||||
secret.id = secret_id
|
secret.id = secret_id
|
||||||
secret.user_id = current_user['id']
|
secret.user_id = current_user['id']
|
||||||
encryption_key = current_user['encryption_key'].encode()
|
encryption_key = current_user['encryption_key'].encode()
|
||||||
|
@ -36,6 +36,6 @@ typer==0.12.3
|
|||||||
typing_extensions==4.12.2
|
typing_extensions==4.12.2
|
||||||
ujson==5.10.0
|
ujson==5.10.0
|
||||||
uvicorn==0.30.1
|
uvicorn==0.30.1
|
||||||
uvloop==0.19.0
|
# uvloop==0.19.0
|
||||||
watchfiles==0.22.0
|
watchfiles==0.22.0
|
||||||
websockets==12.0
|
websockets==12.0
|
||||||
|
Loading…
Reference in New Issue
Block a user