Add register simple user and sessions

This commit is contained in:
Adrian Woźniak 2022-07-06 15:33:14 +02:00
parent bae9f3415c
commit 7abf25345f
No known key found for this signature in database
GPG Key ID: 0012845A89C7352B
27 changed files with 1113 additions and 912 deletions

765
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -4,14 +4,13 @@ version = "0.1.0"
edition = "2021"
[dependencies]
actix = "*"
actix-web = "*"
actix-cors = "*"
actix-rt = "*"
actix-files = "*"
# actix-web-4-validator = { version = "3.2.0", default-features = false }
actix-4-jwt-auth = "0.4.2"
# actix-web-security = "*"
actix = { version = "*" }
actix-web = { version = "*" }
actix-cors = { version = "*" }
actix-rt = { version = "*" }
actix-files = { version = "*" }
actix-redis = { version = "0.11.0" }
actix-identity = { version = "0.4.0" }
askama = { version = "*" }
validator = { version = "0.14", features = ["derive"] }
serde = { version = "*", features = ["derive"] }
@ -19,7 +18,10 @@ serde_json = { version = "*" }
sqlx = { version = "*", features = ["runtime-actix-rustls", "postgres", "uuid", "chrono"] }
uuid = { version = "*", features = ["serde"] }
chrono = { version = "*", features = ["serde"] }
gumdrop = "*"
tracing = "*"
tracing-subscriber = "*"
tracing-actix-web = "*"
gumdrop = { version = "*" }
tracing = { version = "*" }
tracing-subscriber = { version = "*" }
tracing-actix-web = { version = "*" }
argon2 = { version = "0.4.1" }
password-hash = { version = "0.4.2" }
rand = { version = "0.8.5", features = [] }

View File

@ -1,30 +1,15 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<title>OS Wilno</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/assets/css/reset.css" rel="stylesheet"/>
<link href="/assets/css/app.css" rel="stylesheet"/>
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico" />
<script type="module" src=/assets/js/app.js></script>
</head>
<body>
<main>
<header>
<div class="bg">
<h1>OS Wilno</h1>
</div>
</header>
<article>
<ow-nav>
<ow-path path="/">Lokalne Usługi</ow-path>
<ow-path path="/">Aktualności</ow-path>
<ow-path path="/account" selected="selected">Konto</ow-path>
</ow-nav>
<ow-account>
</ow-account>
</article>
</main>
</body>
</html>
{% extends "base.html" %}
{% block content %}
{% match account.as_ref() %}
{% when Some with (a) %}
<ow-account
mode="display"
id="{{a.id}}"
name="{{a.login}}"
email="{{a.email}}"
facebook-id="{{a.facebook_id.as_deref().unwrap_or_default()}}"
></ow-account>
{% when None %}
<ow-account></ow-account>
{% endmatch %}
{% endblock %}

View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<title>OS Wilno</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/assets/css/reset.css" rel="stylesheet"/>
<link href="/assets/css/app.css" rel="stylesheet"/>
<link rel="icon" type="image/x-icon" href="/assets/images/favicon.ico" />
<script type="module" src=/assets/js/app.js></script>
{% block head %}{% endblock %}
</head>
<body>
<main>
<header>
<div class="bg">
<h1>OS Wilno</h1>
</div>
</header>
<article>
<ow-nav>
<ow-path path="/" selected="{{ page.select_index() }}">Lokalne Usługi</ow-path>
<ow-path path="/news" selected="{{ page.select_news() }}">Aktualności</ow-path>
<ow-path path="/account" selected="{{ page.select_account() }}">Konto</ow-path>
</ow-nav>
{% block content %}{% endblock %}
</article>
</main>
</body>
</html>

View File

@ -1,26 +1,5 @@
<!DOCTYPE html>
<html lang="pl">
<head>
<title>OS Wilno</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/assets/css/reset.css" rel="stylesheet"/>
<link href="/assets/css/app.css" rel="stylesheet"/>
<script type="module" src=/assets/js/app.js></script>
</head>
<body>
<main>
<header>
<div class="bg">
<h1>OS Wilno</h1>
</div>
</header>
<article>
<ow-nav>
<ow-path path="/" selected="selected">Lokalne Usługi</ow-path>
<ow-path path="/">Aktualności</ow-path>
<ow-path path="/account">Konto</ow-path>
</ow-nav>
{% extends "base.html" %}
{% block content %}
<local-services>
{% for service in services %}
<local-service
@ -41,7 +20,4 @@
</local-service>
{% endfor %}
</local-services>
</article>
</main>
</body>
</html>
{% endblock %}

282
client/dist/app.js vendored
View File

@ -3,9 +3,10 @@ const FORM_STYLE = `
form {
display: block;
}
form.inline {
display: flex;
justify-content: space-between;
form legend {
margin: 16px 0;
font-weight: bold;
font-size: 20px;
}
form > div {
display: block;
@ -102,12 +103,9 @@ class PseudoForm extends HTMLElement {
}
}
const fireFbReady = ()=>{
for (let c of (b = !0, a))c();
for (let c of (!0, a))c();
};
const runFbReady = (c)=>{
b ? c() : a.push(c);
};
let a = [], b = !1;
let a = [];
customElements.define("form-navigation", class extends HTMLElement {
static get observedAttributes() {
return [
@ -133,7 +131,7 @@ customElements.define("form-navigation", class extends HTMLElement {
display: none !important;
}
</style>
<form class="inline">
<form>
<div class="actions">
<input id="prev" type="button" value="Wróć" />
<input id="next" type="submit" value="Następny" />
@ -355,19 +353,23 @@ customElements.define("login-form", class extends HTMLElement {
customElements.define("ow-account", class extends HTMLElement {
static get observedAttributes() {
return [
"mode"
"mode",
"id",
"name",
"email",
"facebook-id"
];
}
constructor(){
super();
let b = this[S] = this.attachShadow({
let c = this[S] = this.attachShadow({
mode: "closed"
});
b.innerHTML = `
c.innerHTML = `
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
article > * {
#form > * {
display: none;
}
:host([mode="login"]) login-form, :host([mode="login"]) #switch-register {
@ -376,6 +378,12 @@ customElements.define("ow-account", class extends HTMLElement {
:host([mode="register"]) register-form, :host([mode="register"]) #switch-login {
display: block;
}
#display {
display: none;
}
:host([mode="display"]) #display { display: block; }
:host([mode="display"]) #form { display: none; }
a{
display: block;
cursor: pointer;
@ -385,8 +393,10 @@ customElements.define("ow-account", class extends HTMLElement {
border-bottom-color: var(--hover-color);
text-decoration: none;
}
${FORM_STYLE}
</style>
<article>
<article id="form">
<login-form></login-form>
<register-form></register-form>
<section id="switch-register">
@ -396,23 +406,77 @@ customElements.define("ow-account", class extends HTMLElement {
<a>Masz konta? Zaloguj się</a>
</section>
</article>
`, b.querySelector("#switch-login > a").addEventListener("click", (a)=>{
<article id="display">
<div>
<input id="id" name="id" readonly type="hidden" />
</div>
<div>
<label>Login</label>
<input id="name" name="name" readonly />
</div>
<div>
<label>E-Mail</label>
<input id="email" name="email" readonly />
</div>
<div>
<label>Powiązane konto Facebook</label>
<input id="facebook_id" name="facebook_id" readonly />
</div>
</article>
`, c.querySelector("#switch-login > a").addEventListener("click", (a)=>{
a.stopPropagation(), a.preventDefault(), this.mode = "login";
}), b.querySelector("#switch-register > a").addEventListener("click", (a)=>{
}), c.querySelector("#switch-register > a").addEventListener("click", (a)=>{
a.stopPropagation(), a.preventDefault(), this.mode = "register";
});
}
connectedCallback() {
this.mode = "login";
"" === this.mode && (this.mode = "login");
}
attributeChangedCallback(a, b, c) {
if (b === c) return;
if (b !== c) switch(a){
case "mode":
this.mode = c;
break;
case "id":
this.id = c;
break;
case "name":
this.name = c;
break;
case "email":
this.email = c;
break;
case "facebook-id":
this.facebook_id = c;
}
}
get mode() {
return this.getAttribute("mode");
return this.getAttribute("mode") || "";
}
set mode(a) {
a = "login" === a || "register" === a ? a : "login", this.setAttribute("mode", a);
a = [
"login",
"register",
"display"
].includes(a) ? a : "login", this.setAttribute("mode", a);
}
get name() {
return this.getAttribute("name") || "";
}
set name(b) {
this.setAttribute("name", b), this[S].querySelector("#display #name").value = b;
}
get email() {
return this.getAttribute("email") || "";
}
set email(b) {
this.setAttribute("email", b), this[S].querySelector("#display #email").value = b;
}
get facebook_id() {
return this.getAttribute("facebook-id");
}
set facebook_id(b) {
this.setAttribute("facebook-id", b), this[S].querySelector("#display #facebook_id").value = b;
}
});
customElements.define("ow-nav", class extends HTMLElement {
@ -667,7 +731,7 @@ customElements.define("register-basic-form", class extends PseudoForm {
<label>Hasło</label>
<input name="pass" placeholder="Hasło" type="password" required />
</div>
<form-navigation prev="hidden"></form-navigation>
<form-navigation></form-navigation>
</form>
`;
let d = c.querySelector("form");
@ -737,8 +801,8 @@ customElements.define("register-item-form-row", class extends PseudoForm {
}
get inputs() {
return [
this[S].querySelector("input.item-name").cloneNode(!0),
this[S].querySelector("input.item-price").cloneNode(!0),
this[S].querySelector(".item-name").cloneNode(!0),
this[S].querySelector(".item-price").cloneNode(!0),
];
}
updateNames() {
@ -870,6 +934,7 @@ customElements.define("register-submit-form", class extends PseudoForm {
<label>Description</label>
<input readonly id="preview-description">
</div>
<input type="hidden" name="account_type" id="account_type" />
<div id="items">
</div>
@ -890,6 +955,9 @@ customElements.define("register-submit-form", class extends PseudoForm {
f.setAttribute("readonly", "readonly"), e.appendChild(f), e.appendChild(document.createElement("price-view")).value = g.value, g.setAttribute("readonly", "readonly"), g.setAttribute("type", "hidden"), e.appendChild(g);
}
}
set accountType(a) {
this[S].querySelector("#account_type").value = a;
}
});
customElements.define("register-user-type", class extends HTMLElement {
constructor(){
@ -961,102 +1029,104 @@ customElements.define("register-user-type", class extends HTMLElement {
});
}
});
customElements.define("register-oauth2", class extends HTMLElement {
customElements.define("register-user-form", class extends HTMLElement {
static get observedAttributes() {
return [
"fb-connected"
"mode"
];
}
constructor(){
super();
let a = this.attachShadow({
let c = this[S] = this.attachShadow({
mode: "closed"
});
a.innerHTML = `
c.innerHTML = `
<style>
.loginBtn {
box-sizing: border-box;
position: relative;
/* width: 13em; - apply for fixed size */
margin: 0.2em;
padding: 0 15px 0 46px;
border: none;
text-align: left;
line-height: 34px;
white-space: nowrap;
border-radius: 0.2em;
font-size: 16px;
color: #FFF;
:host { display: block; }
svg { width: 200px; }
* { font-family: 'Noto Sans', sans-serif; }
#icons {
display: flex;
justify-content: space-between;
}
.loginBtn:before {
content: "";
box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
width: 34px;
height: 100%;
#form {
display: none;
}
.loginBtn:focus {
outline: none;
:host([mode="email"]) #form {
display: block;
}
.loginBtn:active {
box-shadow: inset 0 0 0 32px rgba(0,0,0,0.1);
:host([mode="email"]) #email-icon {
display: none;
}
/* Facebook */
:host([fb-connected]) .loginBtn--facebook {
display: none !important;
#icons .option {
display: block;
border-radius: 16px;
padding: 16px;
text-align: center;
cursor: pointer;
}
.loginBtn--facebook {
background-color: #4C69BA;
background-image: linear-gradient(#4C69BA, #3B55A0);
/*font-family: "Helvetica neue", Helvetica Neue, Helvetica, Arial, sans-serif;*/
text-shadow: 0 -1px 0 #354C8C;
}
.loginBtn--facebook:before {
border-right: #364e92 1px solid;
background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png') 6px 6px no-repeat;
}
.loginBtn--facebook:hover,
.loginBtn--facebook:focus {
background-color: #5B7BD5;
background-image: linear-gradient(#5B7BD5, #4864B1);
.option {
min-width: 45%;
}
${FORM_STYLE}
</style>
<button class="loginBtn loginBtn--facebook">
Login with Facebook
</button>
<section id="icons">
<div class="option">
<svg id="email-icon" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path d="M123.25 24.192c0-.018 0-.034-.005-.052s-.007-.063-.009-.094a1.734 1.734 0 0 0-.083-.408c-.006-.018 0-.037-.011-.055s-.01-.015-.013-.023a1.734 1.734 0 0 0-.227-.407c-.021-.028-.043-.053-.066-.08a1.755 1.755 0 0 0-.31-.294c-.012-.009-.022-.02-.034-.028a1.744 1.744 0 0 0-.414-.2c-.034-.012-.068-.022-.1-.032a1.733 1.733 0 0 0-.474-.073H6.5a1.733 1.733 0 0 0-.474.073c-.035.01-.068.02-.1.032a1.744 1.744 0 0 0-.414.2c-.012.008-.022.019-.034.028a1.755 1.755 0 0 0-.31.294c-.022.027-.045.052-.066.08a1.734 1.734 0 0 0-.227.407c0 .008-.01.015-.013.023s-.005.037-.011.055a1.734 1.734 0 0 0-.083.408c0 .032-.009.063-.009.094s-.005.034-.005.052v79.615c0 .023.006.045.007.068a1.737 1.737 0 0 0 .019.188c.008.051.015.1.027.152a1.74 1.74 0 0 0 .056.179c.017.047.033.094.054.139a1.729 1.729 0 0 0 .093.172c.024.04.048.081.075.119a1.743 1.743 0 0 0 .125.152c.033.036.066.072.1.106.021.019.037.042.059.061s.036.017.052.03a1.736 1.736 0 0 0 .452.263c.035.014.071.022.107.033a1.732 1.732 0 0 0 .488.085c.012 0 .023.006.035.006H121.501c.012 0 .023-.006.034-.006a1.732 1.732 0 0 0 .489-.085c.035-.011.07-.019.1-.033a1.736 1.736 0 0 0 .453-.263c.016-.013.036-.017.052-.03s.038-.042.059-.061c.036-.034.069-.069.1-.106a1.743 1.743 0 0 0 .125-.152c.027-.038.051-.078.075-.119a1.729 1.729 0 0 0 .093-.172c.021-.045.037-.092.054-.139a1.74 1.74 0 0 0 .056-.179c.012-.05.019-.1.027-.152a1.737 1.737 0 0 0 .019-.188c0-.023.007-.045.007-.068zM45.8 60.316l17.058 14.677a1.751 1.751 0 0 0 2.283 0L82.2 60.316l35.512 41.741H10.289zM8.25 99.052V28.007l34.9 30.026zm76.6-41.019 34.9-30.026v71.045zm31.931-32.091L81.276 56.493c-.006.005-.014.008-.02.014l-.019.02L64 71.358 46.763 56.527l-.019-.02-.02-.014-35.507-30.551z"/>
</svg>
<form>
<legend>Rejestracja adresem e-mail</legend>
<form id="form" method="post" action="/register">
<input type="hidden" id="facebook_id" name="facebook_id">
<div>
<label>E-Mail</label>
<input type="email" name="email">
<input id="email" type="email" name="email">
</div>
<div>
<label>Login</label>
<input type="text" name="login">
<input id="login" type="text" name="login">
</div>
<div>
<label>Hasło</label>
<input type="password" name="password">
<input id="password" type="password" name="password">
</div>
<div>
<input type="submit" value="Zarejestruj" />
</div>
</form>
<slot></slot>
</div>
<div class="option">
<svg id="fb-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#3B5998" d="M448 512h-87.999c-22.094 0-39.999-17.922-39.999-40V360.001c0-22.094 17.905-39.999 39.999-39.999h12.922c8.32 0 16.844-6.656 19.062-15.031l8.484-31.953c2.484-9.312-2.812-17.023-11.852-17.023H360c-22.094 0-39.999-17.902-39.999-39.988v-16C320.001 177.905 337.906 160 360 160h24.008c8.82 0 16-7.156 16-16v-32.004c0-8.828-7.18-16.004-16-16.004H336c-44.187 0-79.991 35.828-79.991 80.011v40.004c0 22.085-17.922 39.988-40.003 39.988h-7.625c-9.039 0-16.379 7.711-16.379 17.023v31.953c0 8.375 6.746 15.031 15.07 15.031h8.934c22.082 0 40.003 17.905 40.003 39.999V472c0 22.078-17.922 40-40.003 40h-8c-8.844 0-16.004-7.172-16.004-16 0-8.844 7.16-16 16.004-16 8.824 0 16-7.172 16-16v-95.999c0-8.844-7.175-16-16-16H197.51c-20.719 0-37.511-16.578-37.511-37.577v-47.922c0-23.156 18.371-42.512 41.027-42.512h6.98c8.824 0 16-7.16 16-15.984v-40.004c0-57.441 46.559-103.995 103.995-103.995h64.008c22.086 0 39.984 17.902 39.984 39.988v48.004c0 22.085-17.898 40.003-39.984 40.003H352.001v16.004c0 8.824 7.156 15.984 16 15.984h29.969c22.672 0 37.398 19.355 33.062 42.512l-8.992 47.922c-3.93 20.999-23.82 37.577-44.539 37.577h-9.5c-8.844 0-16 7.156-16 16V464c0 8.828 7.156 16 16 16H448c17.672 0 32-14.328 32-32V64.007c0-17.688-14.328-32.003-32-32.003H64.007c-17.672 0-32.003 14.316-32.003 32.003V448c0 17.672 14.332 32 32.003 32h16c8.828 0 16.003 7.156 16.003 16 0 8.828-7.175 16-16.003 16h-16C28.668 512 0 483.344 0 448V64.007C0 28.648 28.668 0 64.007 0H448c35.359 0 64 28.648 64 64.007V448c0 35.344-28.641 64-64 64zm-304.001-32c8.844 0 16 7.156 16 16 0 8.828-7.156 16-16 16-8.828 0-16.004-7.172-16.004-16 0-8.844 7.176-16 16.004-16z"/>
</svg>
</div>
</section>
`;
let d = c.querySelector("form");
c.querySelector("#fb-icon").addEventListener("click", (a)=>{
a.stopPropagation(), a.preventDefault(), this.mode = "facebook", FB.login((a)=>{
"connected" === a.status && FB.api("/me?fields=id,name,email", ({ id: a , name: b , email: c , ...e })=>{
console.log(a, b, c, e), d.querySelector("#email").value = c, d.querySelector("#login").value = b, d.querySelector("#password").value = crypto.randomUUID(), d.querySelector("#facebook_id").value = a, d.submit();
});
}, {
scope: "public_profile,email",
return_scopes: !0
});
}), c.querySelector("#email-icon").addEventListener("click", (a)=>{
a.stopPropagation(), a.preventDefault(), this.mode = "email";
});
}
connectedCallback() {
runFbReady(()=>{
FB.getLoginStatus(({ status: a })=>{
"connected" !== a ? FB.login() : this.setAttribute("fb-connected", "1");
});
});
this.mode = "";
}
attributeChangedCallback(a, b, c) {
b !== c && "mode" === a && ("email" === c || "facebook" === c || "" === c) && (this.mode = c);
}
get mode() {
return this.getAttribute("mode") || "";
}
set mode(a) {
this.setAttribute("mode", a);
}
});
let c = (a, b)=>{
@ -1087,7 +1157,7 @@ customElements.define("register-form", class extends HTMLElement {
:host([step="2"]) #step-2 { display: block; }
:host([step="3"]) #step-3 { display: block; }
:host([step="4"]) #step-4 { display: block; }
:host([step="1000"]) #step-1000 { display: block; }
:host([step="40"]) #step-40 { display: block; }
${FORM_STYLE}
@ -1105,31 +1175,22 @@ customElements.define("register-form", class extends HTMLElement {
display: none;
}
</style>
<article id="host">
</article>
<article>
<register-user-type id="step-0">
</register-user-type>
<register-basic-form id="step-1">
</register-basic-form>
<register-company-form id="step-2">
</register-company-form>
<register-items-form id="step-3">
</register-items-form>
<register-submit-form id="step-4">
</register-submit-form>
<register-oauth2 id="step-1000">
</register-oauth2>
<register-user-type id="step-0"> </register-user-type>
<register-basic-form id="step-1"></register-basic-form>
<register-company-form id="step-2"></register-company-form>
<register-items-form id="step-3"></register-items-form>
<register-submit-form id="step-4"></register-submit-form>
<register-user-form id="step-40"></register-user-form>
</article>
`;
let e = d.querySelector("#step-4");
this[S].addEventListener("account:type:user", (a)=>{
a.stopPropagation(), this.step = 1000;
a.stopPropagation(), e.accountType = "User", this.step = 40;
}), this[S].addEventListener("account:type:local-service", (a)=>{
a.stopPropagation(), this.step = 1;
a.stopPropagation(), e.accountType = "Business", this.step = 1;
}), this[S].addEventListener("form:next", (a)=>{
a.stopPropagation();
let b = d.querySelector(`#step-${this.step}`);
@ -1154,14 +1215,15 @@ customElements.define("register-form", class extends HTMLElement {
a < 0 || this.setAttribute("step", a);
}
});
if (window.fbAsyncInit = ()=>{
if (!document.querySelector("#facebook-jssdk")) {
window.fbAsyncInit = ()=>{
FB.init({
appId: "1293538251053124",
cookie: !0,
xfbml: !0,
version: "v14.0"
}), FB.AppEvents.logPageView(), fireFbReady();
}, !document.querySelector("#facebook-jssdk")) {
let b1 = document.createElement("script");
b1.id = "facebook-jssdk", b1.src = "https://connect.facebook.net/en_US/sdk.js", document.head.appendChild(b1);
};
let b = document.createElement("script");
b.id = "facebook-jssdk", b.src = "https://connect.facebook.net/en_US/sdk.js", document.head.appendChild(b);
}

File diff suppressed because one or more lines are too long

View File

@ -4,13 +4,14 @@ import "./local-service-item.js";
import "./local-services.js";
import "./login-form.js";
import "./ow-account.js";
import "./ow-nav.js";
import "./ow-path.js";
import "./nav/ow-nav.js";
import "./nav/ow-path.js";
import "./price/price-view";
import "./price/price-input";
import "./register-form.js";
import { fireFbReady } from "./shared.js";
if (!document.querySelector('#facebook-jssdk')) {
window.fbAsyncInit = () => {
FB.init({
appId: '1293538251053124',
@ -22,11 +23,8 @@ window.fbAsyncInit = () => {
fireFbReady();
};
{
if (!document.querySelector('#facebook-jssdk')) {
const js = document.createElement('script');
js.id = 'facebook-jssdk';
js.src = "https://connect.facebook.net/en_US/sdk.js";
document.head.appendChild(js);
}
}

View File

@ -22,7 +22,7 @@ customElements.define('form-navigation', class extends HTMLElement {
display: none !important;
}
</style>
<form class="inline">
<form>
<div class="actions">
<input id="prev" type="button" value="Wróć" />
<input id="next" type="submit" value="Następny" />

View File

@ -1,8 +1,8 @@
import { S } from "./shared";
import { S, FORM_STYLE } from "./shared";
customElements.define('ow-account', class extends HTMLElement {
static get observedAttributes() {
return ['mode']
return ['mode', "id", "name", 'email', "facebook-id"]
}
constructor() {
@ -12,7 +12,7 @@ customElements.define('ow-account', class extends HTMLElement {
<style>
:host { display: block; }
* { font-family: 'Noto Sans', sans-serif; }
article > * {
#form > * {
display: none;
}
:host([mode="login"]) login-form, :host([mode="login"]) #switch-register {
@ -21,6 +21,12 @@ customElements.define('ow-account', class extends HTMLElement {
:host([mode="register"]) register-form, :host([mode="register"]) #switch-login {
display: block;
}
#display {
display: none;
}
:host([mode="display"]) #display { display: block; }
:host([mode="display"]) #form { display: none; }
a{
display: block;
cursor: pointer;
@ -30,8 +36,10 @@ customElements.define('ow-account', class extends HTMLElement {
border-bottom-color: var(--hover-color);
text-decoration: none;
}
${FORM_STYLE}
</style>
<article>
<article id="form">
<login-form></login-form>
<register-form></register-form>
<section id="switch-register">
@ -41,6 +49,23 @@ customElements.define('ow-account', class extends HTMLElement {
<a>Masz konta? Zaloguj się</a>
</section>
</article>
<article id="display">
<div>
<input id="id" name="id" readonly type="hidden" />
</div>
<div>
<label>Login</label>
<input id="name" name="name" readonly />
</div>
<div>
<label>E-Mail</label>
<input id="email" name="email" readonly />
</div>
<div>
<label>Powiązane konto Facebook</label>
<input id="facebook_id" name="facebook_id" readonly />
</div>
</article>
`;
shadow.querySelector('#switch-login > a').addEventListener('click', ev => {
ev.stopPropagation();
@ -55,21 +80,68 @@ customElements.define('ow-account', class extends HTMLElement {
}
connectedCallback() {
this.mode = 'login';
if (this.mode === '') this.mode = 'login';
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'mode': {
this.mode = newV;
break;
}
case 'id': {
this.id = newV;
break;
}
case 'name': {
this.name = newV;
break;
}
case 'email': {
this.email = newV;
break;
}
case 'facebook-id': {
this.facebook_id = newV;
break;
}
}
}
get mode() {
return this.getAttribute('mode');
return this.getAttribute('mode') || '';
}
set mode(value) {
value = value === 'login' || value === 'register' ? value : 'login';
value = ['login', 'register', 'display'].includes(value) ? value : 'login';
this.setAttribute('mode', value);
}
get name() {
return this.getAttribute('name') || '';
}
set name(v) {
this.setAttribute('name', v);
this[S].querySelector('#display #name').value = v;
}
get email() {
return this.getAttribute('email') || '';
}
set email(v) {
this.setAttribute('email', v);
this[S].querySelector('#display #email').value = v;
}
get facebook_id() {
return this.getAttribute('facebook-id');
}
set facebook_id(v) {
this.setAttribute('facebook-id', v);
this[S].querySelector('#display #facebook_id').value = v;
}
});

View File

@ -6,7 +6,7 @@ import "./register-form/register-items-form.js";
import "./register-form/register-company-form";
import "./register-form/register-submit-form";
import "./register-form/register-user-type";
import "./register-form/register-oauth2";
import "./register-form/register-user-form";
const copyForm = (form, finalForm) => {
form.reportValidity();
@ -46,7 +46,7 @@ customElements.define('register-form', class extends HTMLElement {
:host([step="2"]) #step-2 { display: block; }
:host([step="3"]) #step-3 { display: block; }
:host([step="4"]) #step-4 { display: block; }
:host([step="1000"]) #step-1000 { display: block; }
:host([step="40"]) #step-40 { display: block; }
${ FORM_STYLE }
@ -64,34 +64,27 @@ customElements.define('register-form', class extends HTMLElement {
display: none;
}
</style>
<article id="host">
</article>
<article>
<register-user-type id="step-0">
</register-user-type>
<register-basic-form id="step-1">
</register-basic-form>
<register-company-form id="step-2">
</register-company-form>
<register-items-form id="step-3">
</register-items-form>
<register-submit-form id="step-4">
</register-submit-form>
<register-oauth2 id="step-1000">
</register-oauth2>
<register-user-type id="step-0"> </register-user-type>
<register-basic-form id="step-1"></register-basic-form>
<register-company-form id="step-2"></register-company-form>
<register-items-form id="step-3"></register-items-form>
<register-submit-form id="step-4"></register-submit-form>
<register-user-form id="step-40"></register-user-form>
</article>
`;
const finalForm = shadow.querySelector('#step-4');
this[S].addEventListener('account:type:user', ev => {
ev.stopPropagation();
this.step = 1000;
finalForm.accountType = 'User';
this.step = 40;
});
this[S].addEventListener('account:type:local-service', ev => {
ev.stopPropagation();
finalForm.accountType = 'Business';
this.step = 1;
});
this[S].addEventListener('form:next', ev => {
@ -106,8 +99,7 @@ customElements.define('register-form', class extends HTMLElement {
this.step = this.step - 1;
});
{
const el = finalForm;
el.addEventListener('submit', ev => {
finalForm.addEventListener('submit', ev => {
ev.preventDefault();
ev.stopPropagation();
});

View File

@ -26,7 +26,7 @@ customElements.define('register-basic-form', class extends PseudoForm {
<label>Hasło</label>
<input name="pass" placeholder="Hasło" type="password" required />
</div>
<form-navigation prev="hidden"></form-navigation>
<form-navigation></form-navigation>
</form>
`;

View File

@ -68,8 +68,8 @@ customElements.define('register-item-form-row', class extends PseudoForm {
get inputs() {
return [
this[S].querySelector('input.item-name').cloneNode(true),
this[S].querySelector('input.item-price').cloneNode(true),
this[S].querySelector('.item-name').cloneNode(true),
this[S].querySelector('.item-price').cloneNode(true),
];
}

View File

@ -1,101 +0,0 @@
import { runFbReady } from "../shared";
customElements.define('register-oauth2', class extends HTMLElement {
static get observedAttributes() {
return ['fb-connected']
}
constructor() {
super();
const shadow = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
<style>
.loginBtn {
box-sizing: border-box;
position: relative;
/* width: 13em; - apply for fixed size */
margin: 0.2em;
padding: 0 15px 0 46px;
border: none;
text-align: left;
line-height: 34px;
white-space: nowrap;
border-radius: 0.2em;
font-size: 16px;
color: #FFF;
}
.loginBtn:before {
content: "";
box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
width: 34px;
height: 100%;
}
.loginBtn:focus {
outline: none;
}
.loginBtn:active {
box-shadow: inset 0 0 0 32px rgba(0,0,0,0.1);
}
/* Facebook */
:host([fb-connected]) .loginBtn--facebook {
display: none !important;
}
.loginBtn--facebook {
background-color: #4C69BA;
background-image: linear-gradient(#4C69BA, #3B55A0);
/*font-family: "Helvetica neue", Helvetica Neue, Helvetica, Arial, sans-serif;*/
text-shadow: 0 -1px 0 #354C8C;
}
.loginBtn--facebook:before {
border-right: #364e92 1px solid;
background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png') 6px 6px no-repeat;
}
.loginBtn--facebook:hover,
.loginBtn--facebook:focus {
background-color: #5B7BD5;
background-image: linear-gradient(#5B7BD5, #4864B1);
}
</style>
<button class="loginBtn loginBtn--facebook">
Login with Facebook
</button>
<form>
<legend>Rejestracja adresem e-mail</legend>
<div>
<label>E-Mail</label>
<input type="email" name="email">
</div>
<div>
<label>Login</label>
<input type="text" name="login">
</div>
<div>
<label>Hasło</label>
<input type="password" name="password">
</div>
<div>
<input type="submit" value="Zarejestruj" />
</div>
</form>
<slot></slot>
`;
}
connectedCallback() {
runFbReady(() => {
FB.getLoginStatus(({ status }) => {
if (status !== 'connected')
FB.login();
else
this.setAttribute('fb-connected', '1');
});
});
}
});

View File

@ -40,6 +40,7 @@ customElements.define('register-submit-form', class extends PseudoForm {
<label>Description</label>
<input readonly id="preview-description">
</div>
<input type="hidden" name="account_type" id="account_type" />
<div id="items">
</div>
@ -72,4 +73,8 @@ customElements.define('register-submit-form', class extends PseudoForm {
el.appendChild(price);
}
}
set accountType(v) {
this[S].querySelector('#account_type').value = v;
}
});

View File

@ -0,0 +1,127 @@
import { S, FORM_STYLE } from "../shared";
customElements.define('register-user-form', class extends HTMLElement {
static get observedAttributes() {
return ['mode']
}
constructor() {
super();
const shadow = this[S] = this.attachShadow({ mode: "closed" });
shadow.innerHTML = `
<style>
:host { display: block; }
svg { width: 200px; }
* { font-family: 'Noto Sans', sans-serif; }
#icons {
display: flex;
justify-content: space-between;
}
#form {
display: none;
}
:host([mode="email"]) #form {
display: block;
}
:host([mode="email"]) #email-icon {
display: none;
}
#icons .option {
display: block;
border-radius: 16px;
padding: 16px;
text-align: center;
cursor: pointer;
}
.option {
min-width: 45%;
}
${ FORM_STYLE }
</style>
<section id="icons">
<div class="option">
<svg id="email-icon" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128">
<path d="M123.25 24.192c0-.018 0-.034-.005-.052s-.007-.063-.009-.094a1.734 1.734 0 0 0-.083-.408c-.006-.018 0-.037-.011-.055s-.01-.015-.013-.023a1.734 1.734 0 0 0-.227-.407c-.021-.028-.043-.053-.066-.08a1.755 1.755 0 0 0-.31-.294c-.012-.009-.022-.02-.034-.028a1.744 1.744 0 0 0-.414-.2c-.034-.012-.068-.022-.1-.032a1.733 1.733 0 0 0-.474-.073H6.5a1.733 1.733 0 0 0-.474.073c-.035.01-.068.02-.1.032a1.744 1.744 0 0 0-.414.2c-.012.008-.022.019-.034.028a1.755 1.755 0 0 0-.31.294c-.022.027-.045.052-.066.08a1.734 1.734 0 0 0-.227.407c0 .008-.01.015-.013.023s-.005.037-.011.055a1.734 1.734 0 0 0-.083.408c0 .032-.009.063-.009.094s-.005.034-.005.052v79.615c0 .023.006.045.007.068a1.737 1.737 0 0 0 .019.188c.008.051.015.1.027.152a1.74 1.74 0 0 0 .056.179c.017.047.033.094.054.139a1.729 1.729 0 0 0 .093.172c.024.04.048.081.075.119a1.743 1.743 0 0 0 .125.152c.033.036.066.072.1.106.021.019.037.042.059.061s.036.017.052.03a1.736 1.736 0 0 0 .452.263c.035.014.071.022.107.033a1.732 1.732 0 0 0 .488.085c.012 0 .023.006.035.006H121.501c.012 0 .023-.006.034-.006a1.732 1.732 0 0 0 .489-.085c.035-.011.07-.019.1-.033a1.736 1.736 0 0 0 .453-.263c.016-.013.036-.017.052-.03s.038-.042.059-.061c.036-.034.069-.069.1-.106a1.743 1.743 0 0 0 .125-.152c.027-.038.051-.078.075-.119a1.729 1.729 0 0 0 .093-.172c.021-.045.037-.092.054-.139a1.74 1.74 0 0 0 .056-.179c.012-.05.019-.1.027-.152a1.737 1.737 0 0 0 .019-.188c0-.023.007-.045.007-.068zM45.8 60.316l17.058 14.677a1.751 1.751 0 0 0 2.283 0L82.2 60.316l35.512 41.741H10.289zM8.25 99.052V28.007l34.9 30.026zm76.6-41.019 34.9-30.026v71.045zm31.931-32.091L81.276 56.493c-.006.005-.014.008-.02.014l-.019.02L64 71.358 46.763 56.527l-.019-.02-.02-.014-35.507-30.551z"/>
</svg>
<form id="form" method="post" action="/register">
<input type="hidden" id="facebook_id" name="facebook_id">
<div>
<label>E-Mail</label>
<input id="email" type="email" name="email">
</div>
<div>
<label>Login</label>
<input id="login" type="text" name="login">
</div>
<div>
<label>Hasło</label>
<input id="password" type="password" name="password">
</div>
<div>
<input type="submit" value="Zarejestruj" />
</div>
</form>
</div>
<div class="option">
<svg id="fb-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" xml:space="preserve">
<path fill-rule="evenodd" clip-rule="evenodd" fill="#3B5998" d="M448 512h-87.999c-22.094 0-39.999-17.922-39.999-40V360.001c0-22.094 17.905-39.999 39.999-39.999h12.922c8.32 0 16.844-6.656 19.062-15.031l8.484-31.953c2.484-9.312-2.812-17.023-11.852-17.023H360c-22.094 0-39.999-17.902-39.999-39.988v-16C320.001 177.905 337.906 160 360 160h24.008c8.82 0 16-7.156 16-16v-32.004c0-8.828-7.18-16.004-16-16.004H336c-44.187 0-79.991 35.828-79.991 80.011v40.004c0 22.085-17.922 39.988-40.003 39.988h-7.625c-9.039 0-16.379 7.711-16.379 17.023v31.953c0 8.375 6.746 15.031 15.07 15.031h8.934c22.082 0 40.003 17.905 40.003 39.999V472c0 22.078-17.922 40-40.003 40h-8c-8.844 0-16.004-7.172-16.004-16 0-8.844 7.16-16 16.004-16 8.824 0 16-7.172 16-16v-95.999c0-8.844-7.175-16-16-16H197.51c-20.719 0-37.511-16.578-37.511-37.577v-47.922c0-23.156 18.371-42.512 41.027-42.512h6.98c8.824 0 16-7.16 16-15.984v-40.004c0-57.441 46.559-103.995 103.995-103.995h64.008c22.086 0 39.984 17.902 39.984 39.988v48.004c0 22.085-17.898 40.003-39.984 40.003H352.001v16.004c0 8.824 7.156 15.984 16 15.984h29.969c22.672 0 37.398 19.355 33.062 42.512l-8.992 47.922c-3.93 20.999-23.82 37.577-44.539 37.577h-9.5c-8.844 0-16 7.156-16 16V464c0 8.828 7.156 16 16 16H448c17.672 0 32-14.328 32-32V64.007c0-17.688-14.328-32.003-32-32.003H64.007c-17.672 0-32.003 14.316-32.003 32.003V448c0 17.672 14.332 32 32.003 32h16c8.828 0 16.003 7.156 16.003 16 0 8.828-7.175 16-16.003 16h-16C28.668 512 0 483.344 0 448V64.007C0 28.648 28.668 0 64.007 0H448c35.359 0 64 28.648 64 64.007V448c0 35.344-28.641 64-64 64zm-304.001-32c8.844 0 16 7.156 16 16 0 8.828-7.156 16-16 16-8.828 0-16.004-7.172-16.004-16 0-8.844 7.176-16 16.004-16z"/>
</svg>
</div>
</section>
`;
const form = shadow.querySelector('form');
shadow.querySelector('#fb-icon').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
this.mode = 'facebook';
FB.login((res) => {
if (res.status === 'connected') {
FB.api("/me?fields=id,name,email", ({ id, name, email, ...rest }) => {
console.log(id, name, email, rest);
form.querySelector('#email').value = email;
form.querySelector('#login').value = name;
form.querySelector('#password').value = crypto.randomUUID();
form.querySelector('#facebook_id').value = id;
form.submit();
});
}
}, { scope: 'public_profile,email', return_scopes: true });
});
shadow.querySelector('#email-icon').addEventListener('click', ev => {
ev.stopPropagation();
ev.preventDefault();
this.mode = 'email';
});
}
connectedCallback() {
this.mode = '';
}
attributeChangedCallback(name, oldV, newV) {
if (oldV === newV) return;
switch (name) {
case 'mode': {
if (newV !== 'email' && newV !== 'facebook' && newV !== '')
return;
this.mode = newV;
break;
}
}
}
get mode() {
return this.getAttribute('mode') || ''
}
set mode(v) {
this.setAttribute('mode', v);
}
});

View File

@ -4,9 +4,10 @@ export const FORM_STYLE = `
form {
display: block;
}
form.inline {
display: flex;
justify-content: space-between;
form legend {
margin: 16px 0;
font-weight: bold;
font-size: 20px;
}
form > div {
display: block;

View File

@ -0,0 +1 @@
ALTER TABLE accounts ADD COLUMN facebook_id TEXT DEFAULT NULL;

View File

@ -0,0 +1,8 @@
CREATE TYPE "AccountType" AS ENUM (
'User',
'Business',
'Admin'
);
ALTER TABLE accounts
ADD COLUMN account_type "AccountType" default 'User';

View File

@ -1,14 +1,17 @@
#![feature(drain_filter)]
use actix_web::{App, HttpServer, web::Data};
use crate::routes::render_index;
use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::{web, web::Data, App, HttpResponse, HttpServer};
mod auth;
mod model;
mod routes;
mod utils;
#[actix_web::main] // or #[tokio::main]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
use actix_web::middleware::Logger;
use tracing::{Level};
use tracing::Level;
use tracing_subscriber::FmtSubscriber;
let subscriber = FmtSubscriber::builder()
.with_max_level(Level::DEBUG)
@ -23,10 +26,17 @@ async fn main() -> std::io::Result<()> {
.unwrap();
HttpServer::new(move || {
let policy = CookieIdentityPolicy::new(&[0; 32])
.name("auth-cookie")
.secure(false);
App::new()
.wrap(Logger::default())
.wrap(actix_web::middleware::Compress::default())
.wrap(actix_web::middleware::Logger::default())
.wrap(IdentityService::new(policy))
.app_data(Data::new(pool.clone()))
.configure(routes::configure)
.default_service(web::to(render_index))
})
.bind(("0.0.0.0", 8080))?
.run()

View File

@ -4,12 +4,21 @@ use sqlx::{FromRow, Type};
use std::collections::HashMap;
use uuid::Uuid;
#[derive(Debug, PartialOrd, PartialEq, Serialize, Deserialize, Type)]
pub enum AccountType {
User,
Business,
Admin,
}
#[derive(Debug, Serialize, Deserialize, FromRow)]
pub struct Account {
pub id: i32,
pub login: String,
pub email: String,
pub pass: String,
pub facebook_id: Option<String>,
pub account_type: AccountType,
}
#[derive(Debug, Serialize, Deserialize, Type)]

View File

@ -1,6 +1,41 @@
use crate::model::db;
use serde::{Deserialize, Serialize};
#[derive(Debug)]
pub enum Page {
LocalServices,
News,
Account,
Register,
Login,
}
impl Page {
pub fn select_index(&self) -> &str {
if matches!(self, Page::LocalServices) {
"selected"
} else {
""
}
}
pub fn select_news(&self) -> &str {
if matches!(self, Page::News) {
"selected"
} else {
""
}
}
pub fn select_account(&self) -> &str {
if matches!(self, Page::Account) {
"selected"
} else {
""
}
}
}
#[derive(Debug, Serialize, Deserialize)]
pub struct LocalService {
pub id: i32,

View File

@ -2,6 +2,8 @@ use actix_web::web::ServiceConfig;
mod unrestricted;
pub use unrestricted::render_index;
pub fn configure(config: &mut ServiceConfig) {
unrestricted::configure(config);
}

View File

@ -1,17 +1,43 @@
use crate::model::db;
use crate::model::db::AccountType;
use crate::model::view::Page;
use crate::utils;
use actix_files::Files;
use actix_identity::Identity;
use actix_web::web::{Data, ServiceConfig};
use actix_web::*;
use askama::Template;
use serde::Deserialize;
#[derive(Template)]
#[template(path = "index.html")]
pub struct IndexTemplate {
pub services: Vec<crate::model::view::LocalService>,
services: Vec<crate::model::view::LocalService>,
account: Option<db::Account>,
error: Option<String>,
page: Page,
}
pub async fn render_index() -> HttpResponse {
HttpResponse::NotFound().body(
IndexTemplate {
services: vec![],
account: None,
error: None,
page: Page::LocalServices,
}
.render()
.unwrap(),
)
}
#[get("/")]
pub async fn index(db: Data<sqlx::PgPool>) -> HttpResponse {
pub async fn index(db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
let pool = db.into_inner();
let record = match id.identity() {
Some(id) => utils::user_by_id(id, &pool).await,
_ => None,
};
let (services, mut items) = {
use crate::model::db::{LocalService, LocalServiceItem};
let services: Vec<LocalService> = sqlx::query_as(
@ -62,17 +88,194 @@ FROM local_service_items
.collect()
};
let body = IndexTemplate { services }.render().unwrap();
let body = IndexTemplate {
services,
account: record,
error: None,
page: Page::LocalServices,
}
.render()
.unwrap();
HttpResponse::Ok().body(body)
}
#[derive(Template)]
#[template(path = "account.html")]
struct AccountTemplate;
struct AccountTemplate {
account: Option<db::Account>,
error: Option<String>,
page: Page,
}
#[get("/account")]
async fn account() -> HttpResponse {
HttpResponse::Ok().body(AccountTemplate.render().unwrap())
async fn account_page(id: Identity, db: Data<sqlx::PgPool>) -> HttpResponse {
let pool = db.into_inner();
let record = match id.identity() {
Some(id) => utils::user_by_id(id, &pool).await,
_ => None,
};
HttpResponse::Ok().body(
AccountTemplate {
account: record,
error: None,
page: Page::Account,
}
.render()
.unwrap(),
)
}
#[derive(Debug, Deserialize)]
struct RegisterForm {
email: String,
login: String,
password: String,
facebook_id: Option<String>,
account_type: db::AccountType,
}
#[post("/register")]
async fn register(
form: web::Form<RegisterForm>,
db: Data<sqlx::PgPool>,
id: Identity,
) -> HttpResponse {
let form = form.into_inner();
let pool = db.into_inner();
if form.account_type == AccountType::Admin {
return HttpResponse::BadRequest().body("Breach attempt detected!");
}
let pass = match utils::encrypt(&form.password) {
Ok(pass) => pass,
Err(e) => {
tracing::error!("{:?}", e);
return HttpResponse::BadRequest().body(
AccountTemplate {
account: None,
error: Some("Zapisanie hasła nie powiodło się".into()),
page: Page::Register,
}
.render()
.unwrap(),
);
}
};
let res: sqlx::Result<db::Account> = sqlx::query_as(
r#"
INSERT INTO accounts (login, email, pass, facebook_id, account_type)
VALUES ($1, $2, $3, $4)
RETURNING id, login, email, pass, facebook_id, account_type
"#,
)
.bind(form.login)
.bind(form.email)
.bind(pass)
.bind(form.facebook_id)
.fetch_one(&*pool)
.await;
match res {
Ok(res) => {
id.remember(format!("{}", res.id));
HttpResponse::Ok().body(
AccountTemplate {
account: Some(res),
error: None,
page: Page::Register,
}
.render()
.unwrap(),
)
}
Err(e) => {
eprintln!("{e}");
HttpResponse::BadRequest().body(
AccountTemplate {
account: None,
error: Some("Problem z utworzeniem konta".into()),
page: Page::Register,
}
.render()
.unwrap(),
)
}
}
}
#[post("/logout")]
async fn logout(id: Identity) -> HttpResponse {
id.forget();
HttpResponse::SeeOther()
.append_header(("Location", "/"))
.body(
IndexTemplate {
services: vec![],
account: None,
error: None,
page: Page::LocalServices,
}
.render()
.unwrap(),
)
}
#[derive(Debug, Deserialize)]
struct LoginForm {
email: String,
password: String,
}
#[post("/login")]
async fn login(form: web::Form<LoginForm>, db: Data<sqlx::PgPool>, id: Identity) -> HttpResponse {
let pool = db.into_inner();
let form = form.into_inner();
let record: db::Account = match sqlx::query_as(
r#"
SELECT id, login, email, pass, facebook_id, account_type
FROM accounts
WHERE email = $1
"#,
)
.bind(form.email)
.fetch_one(&*pool)
.await
{
Ok(record) => record,
Err(e) => {
tracing::error!("{e}");
return HttpResponse::Ok().body(
AccountTemplate {
account: None,
error: Some("Nie znaleziono konta".into()),
page: Page::Login,
}
.render()
.unwrap(),
);
}
};
if utils::validate(&form.password, &record.pass).is_err() {
return HttpResponse::BadRequest().body(
AccountTemplate {
account: None,
error: Some("Hasło i/lub adres e-mail są nieprawidłowe".into()),
page: Page::Login,
}
.render()
.unwrap(),
);
}
id.remember(format!("{}", record.id));
HttpResponse::Ok().body(
AccountTemplate {
account: Some(record),
error: None,
page: Page::Login,
}
.render()
.unwrap(),
)
}
pub fn configure(config: &mut ServiceConfig) {
@ -86,5 +289,8 @@ pub fn configure(config: &mut ServiceConfig) {
.show_files_listing(),
)
.service(index)
.service(account);
.service(account_page)
.service(register)
.service(logout)
.service(login);
}

52
src/utils.rs Normal file
View File

@ -0,0 +1,52 @@
use crate::model::db;
use argon2::{Algorithm, Argon2, Params, Version};
use password_hash::{PasswordHash, PasswordHasher, PasswordVerifier, SaltString};
pub fn encrypt(pass: &str) -> password_hash::Result<String> {
tracing::debug!("Hashing password {:?}", pass);
Ok(
Argon2::new(Algorithm::Argon2id, Version::V0x13, Params::default())
.hash_password(pass.as_bytes(), &SaltString::generate(rand::rngs::OsRng))?
.to_string(),
)
}
pub fn validate(pass: &str, pass_hash: &str) -> password_hash::Result<()> {
tracing::debug!("Validating password {:?} {:?}", pass, pass_hash);
Argon2::default().verify_password(
pass.as_bytes(),
&PasswordHash::new(pass_hash).expect("Invalid hashed password"),
)
}
pub async fn user_by_id(id: String, pool: &sqlx::PgPool) -> Option<db::Account> {
match sqlx::query_as(
r#"
SELECT id, login, email, pass, facebook_id, account_type
FROM accounts
WHERE id = $1 :: INT
"#,
)
.bind(id)
.fetch_optional(pool)
.await
{
Ok(res) => res,
Err(e) => {
tracing::error!("{e}");
None
}
}
}
#[cfg(test)]
mod tests {
use crate::utils::{encrypt, validate};
#[test]
fn check_pass() {
let pass = "foobar";
validate(pass, &encrypt(pass).unwrap()).unwrap();
}
}