Added session system
This commit is contained in:
parent
f1cb209217
commit
5f31e4bf22
35
Cargo.lock
generated
35
Cargo.lock
generated
@ -317,6 +317,29 @@ dependencies = [
|
|||||||
"tracing",
|
"tracing",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-extra"
|
||||||
|
version = "0.9.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0be6ea09c9b96cb5076af0de2e383bd2bc0c18f827cf1967bdd353e0b910d733"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"axum-core",
|
||||||
|
"bytes",
|
||||||
|
"cookie",
|
||||||
|
"futures-util",
|
||||||
|
"http 1.1.0",
|
||||||
|
"http-body 1.0.0",
|
||||||
|
"http-body-util",
|
||||||
|
"mime",
|
||||||
|
"pin-project-lite",
|
||||||
|
"serde",
|
||||||
|
"tower",
|
||||||
|
"tower-layer",
|
||||||
|
"tower-service",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-macros"
|
name = "axum-macros"
|
||||||
version = "0.4.1"
|
version = "0.4.1"
|
||||||
@ -822,6 +845,17 @@ dependencies = [
|
|||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cookie"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747"
|
||||||
|
dependencies = [
|
||||||
|
"percent-encoding",
|
||||||
|
"time",
|
||||||
|
"version_check",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation"
|
name = "core-foundation"
|
||||||
version = "0.9.4"
|
version = "0.9.4"
|
||||||
@ -5556,6 +5590,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"argon2",
|
"argon2",
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-extra",
|
||||||
"dioxus",
|
"dioxus",
|
||||||
"dioxus-logger",
|
"dioxus-logger",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
|
@ -16,6 +16,7 @@ once_cell = "1.19.0"
|
|||||||
tokio = { version = "1.38.0", features = [ "macros", "rt-multi-thread" ], optional = true }
|
tokio = { version = "1.38.0", features = [ "macros", "rt-multi-thread" ], optional = true }
|
||||||
argon2 = { version = "0.5.3", optional = true }
|
argon2 = { version = "0.5.3", optional = true }
|
||||||
axum = { version = "0.7.5", optional = true }
|
axum = { version = "0.7.5", optional = true }
|
||||||
|
axum-extra = { version = "0.9.3", features = [ "cookie" ], optional = true}
|
||||||
|
|
||||||
# Debug
|
# Debug
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
@ -23,5 +24,5 @@ dioxus-logger = "0.5.0"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
server = ["dioxus/axum", "axum", "surrealdb", "tokio", "argon2" ]
|
server = ["dioxus/axum", "axum", "axum-extra", "surrealdb", "tokio", "argon2" ]
|
||||||
web = ["dioxus/web"]
|
web = ["dioxus/web"]
|
||||||
|
@ -766,15 +766,6 @@ html {
|
|||||||
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
|
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu li > *:not(ul, .menu-title, details, .btn):active,
|
|
||||||
.menu li > *:not(ul, .menu-title, details, .btn).active,
|
|
||||||
.menu li > details > summary:active {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab:hover {
|
.tab:hover {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
}
|
}
|
||||||
@ -783,6 +774,12 @@ html {
|
|||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table tr.hover:hover,
|
||||||
|
.table tr.hover:nth-child(even):hover {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn {
|
.btn {
|
||||||
@ -980,18 +977,6 @@ html {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(.active, .btn):hover, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(.active, .btn):hover {
|
|
||||||
cursor: pointer;
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@supports (color: oklch(0% 0 0)) {
|
|
||||||
:where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(.active, .btn):hover, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(.active, .btn):hover {
|
|
||||||
background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab[disabled],
|
.tab[disabled],
|
||||||
.tab[disabled]:hover {
|
.tab[disabled]:hover {
|
||||||
cursor: not-allowed;
|
cursor: not-allowed;
|
||||||
@ -1066,12 +1051,6 @@ html {
|
|||||||
margin-inline-end: -1rem;
|
margin-inline-end: -1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.input-sm[type="number"]::-webkit-inner-spin-button {
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
margin-inline-end: -0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
text-decoration-line: underline;
|
text-decoration-line: underline;
|
||||||
@ -1081,59 +1060,6 @@ html {
|
|||||||
text-decoration-line: none;
|
text-decoration-line: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.25rem;
|
|
||||||
padding: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu :where(li ul) {
|
|
||||||
position: relative;
|
|
||||||
white-space: nowrap;
|
|
||||||
margin-inline-start: 1rem;
|
|
||||||
padding-inline-start: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)), .menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
|
|
||||||
display: grid;
|
|
||||||
grid-auto-flow: column;
|
|
||||||
align-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.5rem;
|
|
||||||
grid-auto-columns: minmax(auto, max-content) auto max-content;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu li.disabled {
|
|
||||||
cursor: not-allowed;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
color: var(--fallback-bc,oklch(var(--bc)/0.3));
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu :where(li > .menu-dropdown:not(.menu-dropdown-show)) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(.menu li) {
|
|
||||||
position: relative;
|
|
||||||
display: flex;
|
|
||||||
flex-shrink: 0;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: stretch;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(.menu li) .badge {
|
|
||||||
justify-self: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.navbar {
|
.navbar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -1147,11 +1073,6 @@ html {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navbar-end {
|
|
||||||
width: 50%;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs {
|
.tabs {
|
||||||
display: grid;
|
display: grid;
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
@ -1216,6 +1137,39 @@ input.tab:checked + .tab-content,
|
|||||||
display: block;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
border-radius: var(--rounded-box, 1rem);
|
||||||
|
text-align: left;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
line-height: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table :where(.table-pin-rows thead tr) {
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.table :where(.table-pin-rows tfoot tr) {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0px;
|
||||||
|
z-index: 1;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.table :where(.table-pin-cols tr th) {
|
||||||
|
position: sticky;
|
||||||
|
left: 0px;
|
||||||
|
right: 0px;
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
||||||
|
}
|
||||||
|
|
||||||
.btm-nav > * .label {
|
.btm-nav > * .label {
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
@ -1483,88 +1437,6 @@ input.tab:checked + .tab-content,
|
|||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:where(.menu li:empty) {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
|
|
||||||
opacity: 0.1;
|
|
||||||
margin: 0.5rem 1rem;
|
|
||||||
height: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu :where(li ul):before {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0.75rem;
|
|
||||||
inset-inline-start: 0px;
|
|
||||||
top: 0.75rem;
|
|
||||||
width: 1px;
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: var(--fallback-bc,oklch(var(--bc)/var(--tw-bg-opacity)));
|
|
||||||
opacity: 0.1;
|
|
||||||
content: "";
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu :where(li:not(.menu-title) > *:not(ul, details, .menu-title, .btn)),
|
|
||||||
.menu :where(li:not(.menu-title) > details > summary:not(.menu-title)) {
|
|
||||||
border-radius: var(--rounded-btn, 0.5rem);
|
|
||||||
padding-left: 1rem;
|
|
||||||
padding-right: 1rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
text-align: start;
|
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, -webkit-backdrop-filter;
|
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter;
|
|
||||||
transition-property: color, background-color, border-color, text-decoration-color, fill, stroke, opacity, box-shadow, transform, filter, backdrop-filter, -webkit-backdrop-filter;
|
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
transition-timing-function: cubic-bezier(0, 0, 0.2, 1);
|
|
||||||
transition-duration: 200ms;
|
|
||||||
text-wrap: balance;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(summary, .active, .btn).focus, :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):not(summary, .active, .btn):focus, :where(.menu li:not(.menu-title, .disabled) > *:not(ul, details, .menu-title)):is(summary):not(.active, .btn):focus-visible, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(summary, .active, .btn).focus, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):not(summary, .active, .btn):focus, :where(.menu li:not(.menu-title, .disabled) > details > summary:not(.menu-title)):is(summary):not(.active, .btn):focus-visible {
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: var(--fallback-bc,oklch(var(--bc)/0.1));
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: var(--fallback-bc,oklch(var(--bc)/var(--tw-text-opacity)));
|
|
||||||
outline: 2px solid transparent;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu li > *:not(ul, .menu-title, details, .btn):active,
|
|
||||||
.menu li > *:not(ul, .menu-title, details, .btn).active,
|
|
||||||
.menu li > details > summary:active {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: var(--fallback-n,oklch(var(--n)/var(--tw-bg-opacity)));
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: var(--fallback-nc,oklch(var(--nc)/var(--tw-text-opacity)));
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu :where(li > details > summary)::-webkit-details-marker {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu :where(li > details > summary):after,
|
|
||||||
.menu :where(li > .menu-dropdown-toggle):after {
|
|
||||||
justify-self: end;
|
|
||||||
display: block;
|
|
||||||
margin-top: -0.5rem;
|
|
||||||
height: 0.5rem;
|
|
||||||
width: 0.5rem;
|
|
||||||
transform: rotate(45deg);
|
|
||||||
transition-property: transform, margin-top;
|
|
||||||
transition-duration: 0.3s;
|
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
|
||||||
content: "";
|
|
||||||
transform-origin: 75% 75%;
|
|
||||||
box-shadow: 2px 2px;
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu :where(li > details[open] > summary):after,
|
|
||||||
.menu :where(li > .menu-dropdown-toggle.menu-dropdown-show):after {
|
|
||||||
transform: rotate(225deg);
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mockup-browser .mockup-browser-toolbar .input {
|
.mockup-browser .mockup-browser-toolbar .input {
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
@ -1798,6 +1670,45 @@ input.tab:checked + .tab-content,
|
|||||||
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
color: var(--fallback-pc,oklch(var(--pc)/var(--tw-text-opacity)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
:is([dir="rtl"] .table) {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table :where(th, td) {
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table tr.active,
|
||||||
|
.table tr.active:nth-child(even),
|
||||||
|
.table-zebra tbody tr:nth-child(even) {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-bg-opacity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.table :where(thead tr, tbody tr:not(:last-child),tbody tr:first-child:last-child) {
|
||||||
|
border-bottom-width: 1px;
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-bottom-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
|
||||||
|
}
|
||||||
|
|
||||||
|
.table :where(thead, tfoot) {
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
line-height: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--fallback-bc,oklch(var(--bc)/0.6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.table :where(tfoot) {
|
||||||
|
border-top-width: 1px;
|
||||||
|
--tw-border-opacity: 1;
|
||||||
|
border-top-color: var(--fallback-b2,oklch(var(--b2)/var(--tw-border-opacity)));
|
||||||
|
}
|
||||||
|
|
||||||
@keyframes toast-pop {
|
@keyframes toast-pop {
|
||||||
0% {
|
0% {
|
||||||
transform: scale(0.9);
|
transform: scale(0.9);
|
||||||
@ -1810,44 +1721,6 @@ input.tab:checked + .tab-content,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn-sm {
|
|
||||||
height: 2rem;
|
|
||||||
min-height: 2rem;
|
|
||||||
padding-left: 0.75rem;
|
|
||||||
padding-right: 0.75rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-square:where(.btn-sm) {
|
|
||||||
height: 2rem;
|
|
||||||
width: 2rem;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-circle:where(.btn-sm) {
|
|
||||||
height: 2rem;
|
|
||||||
width: 2rem;
|
|
||||||
border-radius: 9999px;
|
|
||||||
padding: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.input-sm {
|
|
||||||
height: 2rem;
|
|
||||||
padding-left: 0.75rem;
|
|
||||||
padding-right: 0.75rem;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-horizontal {
|
|
||||||
display: inline-flex;
|
|
||||||
flex-direction: row;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-horizontal > li:not(.menu-title) > details > ul {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-md :where(.tab) {
|
.tabs-md :where(.tab) {
|
||||||
height: 2rem;
|
height: 2rem;
|
||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
@ -1890,27 +1763,6 @@ input.tab:checked + .tab-content,
|
|||||||
line-height: 1.5rem;
|
line-height: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-horizontal > li:not(.menu-title) > details > ul {
|
|
||||||
margin-inline-start: 0px;
|
|
||||||
margin-top: 1rem;
|
|
||||||
padding-top: 0.5rem;
|
|
||||||
padding-bottom: 0.5rem;
|
|
||||||
padding-inline-end: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.menu-horizontal > li > details > ul:before {
|
|
||||||
content: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
:where(.menu-horizontal > li:not(.menu-title) > details > ul) {
|
|
||||||
border-radius: var(--rounded-box, 1rem);
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: var(--fallback-b1,oklch(var(--b1)/var(--tw-bg-opacity)));
|
|
||||||
--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
|
|
||||||
--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);
|
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.static {
|
.static {
|
||||||
position: static;
|
position: static;
|
||||||
}
|
}
|
||||||
@ -1928,6 +1780,10 @@ input.tab:checked + .tab-content,
|
|||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
.w-full {
|
.w-full {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
@ -1963,16 +1819,16 @@ input.tab:checked + .tab-content,
|
|||||||
padding-right: 0.25rem;
|
padding-right: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.py-6 {
|
|
||||||
padding-top: 1.5rem;
|
|
||||||
padding-bottom: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-1 {
|
.py-1 {
|
||||||
padding-top: 0.25rem;
|
padding-top: 0.25rem;
|
||||||
padding-bottom: 0.25rem;
|
padding-bottom: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.py-6 {
|
||||||
|
padding-top: 1.5rem;
|
||||||
|
padding-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
.pt-40 {
|
.pt-40 {
|
||||||
padding-top: 10rem;
|
padding-top: 10rem;
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,9 @@ use surrealdb::sql::Thing;
|
|||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
email: String,
|
pub id: String,
|
||||||
password: String,
|
pub email: String,
|
||||||
|
pub password: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
@ -17,10 +18,92 @@ struct Record {
|
|||||||
id: Thing,
|
id: Thing,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize)]
|
||||||
|
struct Session {
|
||||||
|
user_id: String,
|
||||||
|
id: String,
|
||||||
|
expires: i64,
|
||||||
|
token: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl User {
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
async fn from_cookie() -> Result<Self, ServerFnError> {
|
||||||
|
use axum_extra::extract::CookieJar;
|
||||||
|
|
||||||
|
let jar: CookieJar = extract().await?;
|
||||||
|
|
||||||
|
let session_token = jar
|
||||||
|
.get("session_token")
|
||||||
|
.ok_or_else(|| ServerFnError::new("Session token cookie is not set"))?;
|
||||||
|
|
||||||
|
let session_token = session_token.value();
|
||||||
|
|
||||||
|
let mut res = DB
|
||||||
|
.query("SELECT type::string(user.id) as id, user.email as email FROM session WHERE token = $session_token")
|
||||||
|
.bind(("session_token", session_token))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let user: Option<User> = res.take(0)?;
|
||||||
|
|
||||||
|
match user {
|
||||||
|
Some(u) => {
|
||||||
|
tracing::info!("Authorized session for {}", u.id);
|
||||||
|
Ok(u)
|
||||||
|
}
|
||||||
|
None => Err(ServerFnError::ServerError(
|
||||||
|
"Could not authorize session".to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Session {
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
async fn create(user_id: &String) -> Result<Self, ServerFnError> {
|
||||||
|
// Create a session
|
||||||
|
let mut res = DB
|
||||||
|
.query("CREATE session SET user = type::thing($user_id) RETURN type::string(id) as id, type::string(id) as user_id, time::unix(expires) as expires, token;")
|
||||||
|
.bind(("user_id", user_id))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let session: Option<Session> = res.take(0)?;
|
||||||
|
|
||||||
|
match session {
|
||||||
|
Some(s) => {
|
||||||
|
tracing::info!("Created new session for {}", user_id);
|
||||||
|
Ok(s)
|
||||||
|
}
|
||||||
|
None => Err(ServerFnError::ServerError(
|
||||||
|
"Could not generate session".to_string(),
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
fn write(&self) -> Result<(), ServerFnError> {
|
||||||
|
use axum::http::{header, HeaderValue};
|
||||||
|
use axum_extra::extract::cookie::{Cookie, SameSite};
|
||||||
|
|
||||||
|
let mut cookie = Cookie::new("session_token", &self.token);
|
||||||
|
|
||||||
|
cookie.set_same_site(SameSite::Strict);
|
||||||
|
|
||||||
|
server_context()
|
||||||
|
.response_parts_mut()
|
||||||
|
.unwrap()
|
||||||
|
.headers
|
||||||
|
.insert(
|
||||||
|
header::SET_COOKIE,
|
||||||
|
HeaderValue::from_str(&cookie.to_string())?,
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[server(Register)]
|
#[server(Register)]
|
||||||
pub async fn register(email: String, password: String) -> Result<String, ServerFnError> {
|
pub async fn register(email: String, password: String) -> Result<String, ServerFnError> {
|
||||||
tracing::info!("Creating new user");
|
|
||||||
|
|
||||||
let mut res = DB
|
let mut res = DB
|
||||||
.query("CREATE user SET email = $email, password = crypto::argon2::generate($password)")
|
.query("CREATE user SET email = $email, password = crypto::argon2::generate($password)")
|
||||||
.bind(("email", email))
|
.bind(("email", email))
|
||||||
@ -32,6 +115,10 @@ pub async fn register(email: String, password: String) -> Result<String, ServerF
|
|||||||
match user {
|
match user {
|
||||||
Some(Record { id }) => {
|
Some(Record { id }) => {
|
||||||
tracing::info!("Created new user ({id})");
|
tracing::info!("Created new user ({id})");
|
||||||
|
|
||||||
|
let session = Session::create(&id.to_raw()).await?;
|
||||||
|
session.write()?;
|
||||||
|
|
||||||
Ok(id.to_string())
|
Ok(id.to_string())
|
||||||
}
|
}
|
||||||
_ => Err(ServerFnError::ServerError("Could not get id".to_string())),
|
_ => Err(ServerFnError::ServerError("Could not get id".to_string())),
|
||||||
@ -40,8 +127,9 @@ pub async fn register(email: String, password: String) -> Result<String, ServerF
|
|||||||
|
|
||||||
#[server(Signin)]
|
#[server(Signin)]
|
||||||
pub async fn signin(email: String, password: String) -> Result<User, ServerFnError> {
|
pub async fn signin(email: String, password: String) -> Result<User, ServerFnError> {
|
||||||
|
// Find the user with the correct email and password
|
||||||
let mut res = DB
|
let mut res = DB
|
||||||
.query("SELECT * FROM user WHERE email = $email AND crypto::argon2::compare(password, $password)")
|
.query("SELECT type::string(id) as id, email, password FROM user WHERE email = $email AND crypto::argon2::compare(password, $password)")
|
||||||
.bind(("email", email))
|
.bind(("email", email))
|
||||||
.bind(("password", password))
|
.bind(("password", password))
|
||||||
.await?;
|
.await?;
|
||||||
@ -51,8 +139,17 @@ pub async fn signin(email: String, password: String) -> Result<User, ServerFnErr
|
|||||||
match user {
|
match user {
|
||||||
Some(u) => {
|
Some(u) => {
|
||||||
tracing::info!("User ({}) has signed in", u.email);
|
tracing::info!("User ({}) has signed in", u.email);
|
||||||
|
|
||||||
|
let session = Session::create(&u.id).await?;
|
||||||
|
session.write()?;
|
||||||
|
|
||||||
Ok(u)
|
Ok(u)
|
||||||
}
|
}
|
||||||
_ => Err(ServerFnError::ServerError("Could not get id".to_string())),
|
_ => Err(ServerFnError::ServerError("Could not get id".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[server(GetUser)]
|
||||||
|
pub async fn get_current_user() -> Result<User, ServerFnError> {
|
||||||
|
User::from_cookie().await
|
||||||
|
}
|
||||||
|
@ -15,16 +15,30 @@ pub async fn connect() -> surrealdb::Result<()> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn define_schema() -> surrealdb::Result<()> {
|
pub async fn define_schema() -> surrealdb::Result<()> {
|
||||||
|
// Define user table
|
||||||
let sql = "
|
let sql = "
|
||||||
DEFINE TABLE user SCHEMAFULL;
|
DEFINE TABLE user SCHEMAFULL;
|
||||||
|
|
||||||
DEFINE FIELD email ON TABLE user TYPE string
|
DEFINE FIELD email ON TABLE user TYPE string
|
||||||
ASSERT string::is::email($value);
|
ASSERT string::is::email($value) VALUE string::lowercase($value);
|
||||||
DEFINE FIELD password ON TABLE user TYPE string;
|
DEFINE FIELD password ON TABLE user TYPE string;
|
||||||
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;
|
DEFINE INDEX userEmailIndex ON TABLE user COLUMNS email UNIQUE;
|
||||||
";
|
";
|
||||||
|
|
||||||
DB.query(sql).await?;
|
DB.query(sql).await?;
|
||||||
|
|
||||||
|
// Define session table
|
||||||
|
let sql = "
|
||||||
|
DEFINE TABLE session SCHEMAFULL;
|
||||||
|
|
||||||
|
DEFINE FIELD user ON TABLE session TYPE record;
|
||||||
|
DEFINE FIELD token ON TABLE session TYPE string VALUE rand::string(32);
|
||||||
|
DEFINE FIELD expires ON TABLE session TYPE datetime VALUE time::now() + 1w;
|
||||||
|
|
||||||
|
DEFINE INDEX sessionTokenIndex ON TABLE session COLUMNS token UNIQUE;
|
||||||
|
";
|
||||||
|
|
||||||
|
DB.query(sql).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user