frontend toolchain config and registration

This commit is contained in:
realaravinth 2021-04-02 20:35:06 +05:30
parent 584d43c34a
commit ecc0076cdd
No known key found for this signature in database
GPG key ID: AD9F0F08E855ED88
23 changed files with 9320 additions and 197 deletions

2
frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
node_modules/
dist/

33
frontend/package.json Normal file
View file

@ -0,0 +1,33 @@
{
"name": "frontend",
"version": "0.1.0",
"description": "mCaptcha/guard frontend",
"main": "index.js",
"repository": "https://github.com/mCaptcha/guard",
"author": "Aravinth Manivannan <realaravinth@batsense.net>",
"license": "AGPLv3 or above",
"scripts": {
"start": "webpack-dev-server --config webpack.dev.js --open",
"build": "webpack --config webpack.prod.js"
},
"private": true,
"devDependencies": {
"css-loader": "^2.1.0",
"file-loader": "^3.0.1",
"html-loader": "^0.5.5",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.5.0",
"node-sass": "^4.11.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"sass-loader": "^7.1.0",
"style-loader": "^0.23.1",
"webpack": "^4.29.6",
"webpack-cli": "^3.2.3",
"webpack-dev-server": "^3.2.1",
"webpack-merge": "^4.2.1"
},
"dependencies": {
"clean-webpack-plugin": "^2.0.0"
}
}

View file

Before

Width:  |  Height:  |  Size: 1.3 MiB

After

Width:  |  Height:  |  Size: 1.3 MiB

View file

Before

Width:  |  Height:  |  Size: 455 KiB

After

Width:  |  Height:  |  Size: 455 KiB

View file

@ -0,0 +1,38 @@
<html lang="en"><head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login | mCaptcha</title>
</head>
<body>
<div class="form-container">
<img src="/img/icon-trans.png" class="form__logo" alt="">
<h2 class="form__brand">Sign in to mCaptcha</h2>
<form class="form__box" id="form">
<label class="form__in-group" for="username">Username
<input class="form__in-field" id="username" type="text" name="username" required="">
</label>
<label for="password" class="form__in-group">Password
<input class="form__in-field" type="password" id="password" name="password" required="">
<!--
<a class="form__pw-recovery" -href="/recovert/password"
>Forgot password?</a
>
-->
</label>
<button class="form__submit-button" type="submit">
Submit
</button>
</form>
<div class="form__secondary-action">
<p class="form__secondary-action__banner">
New to mCaptcha?
<a href="/signup" class="form__secondary-action__link">Create account</a>
</p>
</div>
</div>
<link rel="stylesheet" href="/css/forms.css">
</body></html>

View file

@ -0,0 +1,75 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login | mCaptcha</title>
</head>
<body>
<div class="form-container">
<img src="/img/icon-trans.png" class="form__logo" alt="" />
<h2 class="form__brand">Join mCaptcha</h2>
<form class="form__box" id="form">
<label class="form__in-group" for="username"
>Username
<input
class="form__in-field"
id="username"
type="text"
name="username"
id="username"
required
/>
</label>
<label class="form__in-group" for="username"
>Email
<input
class="form__in-field"
id="email"
type="email"
name="email"
id="email"
required
/>
</label>
<label for="password" class="form__in-group"
>Password
<input
class="form__in-field"
type="password"
id="password"
name="password"
id="password"
required
/>
</label>
<label for="password" class="form__in-group"
>Re-enter Password
<input
class="form__in-field"
type="password"
id="password-check"
name="password-check"
id="password-check"
required
/>
</label>
<button class="form__submit-button" type="submit">
Submit
</button>
</form>
<div class="form__secondary-action">
<p class="form__secondary-action__banner">
Already have an account?
<a href="/" class="form__secondary-action__link"
>Click here to login</a
>
</p>
</div>
</div>
</body>
</html>

View file

@ -0,0 +1,47 @@
const ROUTES = {
registerUser: '/api/v1/signup',
loginUser: '/api/v1/signin',
signoutUser: '/api/v1/signout',
deleteAccount: '/api/v1/account/delete',
usernameExists: '/api/v1/account/username/exists',
emailExists: '/api/v1/account/email/exists',
healthCheck: '/api/v1/meta/health',
buildDetails: '/api/v1/meta/build',
addDomain: '/api/v1/mcaptcha/domain/add',
challengeDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/get',
proveDomain: '/api/v1/mcaptcha/domain/domain/verify/challenge/prove',
deleteDomain: '/api/v1/mcaptcha/domain/delete',
addToken: '/api/v1/mcaptcha/domain/token/add',
updateTokenKey: '/api/v1/mcaptcha/domain/token/update',
getTokenKey: '/api/v1/mcaptcha/domain/token/get',
deleteToken: '/api/v1/mcaptcha/domain/token/delete',
addTokenLevels: '/api/v1/mcaptcha/domain/token/levels/add',
updateTokenLevels: '/api/v1/mcaptcha/domain/token/levels/update',
deleteTokenLevels: '/api/v1/mcaptcha/domain/token/levels/delete',
getTokenLevels: '/api/v1/mcaptcha/domain/token/levels/get',
getTokenDuration: '/api/v1/mcaptcha/domain/token/token/get',
updateTokenDuration: '/api/v1/mcaptcha/domain/token/token/update',
};
export default ROUTES;

View file

@ -0,0 +1,43 @@
import ROUTES from '../api/v1/routes';
import genJsonPayload from '../utils/genJsonPayload';
const checkEmailExists = async () => {
let email = document.getElementById('email');
let val = email.value;
let payload = {
val,
};
// return fetch(ROUTES.emailExists, genJsonPayload(payload)).then(res => {
// if (res.ok) {
// res.json().then(data => {
// if (data.exists) {
// console.log(email.className);
// email.className += ' form__in-field--warn';
// alert('Email taken');
// }
//
// return data.exists;
// });
// } else {
// res.json().then(err => alert(`error: ${err.error}`));
// }
// });
//
let res = await fetch(ROUTES.emailExists, genJsonPayload(payload));
if (res.ok) {
let data = await res.json();
if (data.exists) {
email.className += ' form__in-field--warn';
alert('Email taken');
}
return data.exists;
} else {
let err = await res.json();
alert(`error: ${err.error}`);
}
};
export {checkEmailExists};

View file

@ -0,0 +1,49 @@
import ROUTES from '../api/v1/routes';
import isBlankString from '../utils/isBlankString';
import genJsonPayload from '../utils/genJsonPayload';
import {checkUsernameExists} from './userExists';
import {checkEmailExists} from './emailExists';
const registerUser = async e => {
e.preventDefault();
let username = document.getElementById('username').value;
isBlankString(e, username, 'username');
let password = document.getElementById('password').value;
let passwordCheck = document.getElementById('password-check').value;
if (password != passwordCheck) {
return alert("passwords don't match, check again!");
}
let email = document.getElementById('email').value;
isBlankString(e, email, 'email');
let exists = await checkUsernameExists();
if (exists) {
return;
}
exists = await checkEmailExists();
if (exists) {
return;
}
let payload = {
username,
password,
email,
};
let res = await fetch(ROUTES.registerUser, genJsonPayload(payload));
if (res.ok) {
alert('success');
} else {
let err = await res.json();
alert(`error: ${err.error}`);
}
};
export default registerUser;

View file

@ -0,0 +1,26 @@
import isBlankString from '../utils/isBlankString';
import genJsonPayload from '../utils/genJsonPayload';
import ROUTES from '../api/v1/routes';
const signin = e => {
e.preventDefault();
let username = document.getElementById('username').value;
isBlankString(e, username, 'username');
let password = document.getElementById('password').value;
let payload = {
username,
password,
};
fetch(ROUTES.loginUser, genJsonPayload(payload)).then(res => {
if (res.ok) {
alert('success');
} else {
res.json().then(err => alert(`error: ${err.error}`));
}
});
};
export default signin;

View file

@ -0,0 +1,47 @@
import ROUTES from '../api/v1/routes';
import genJsonPayload from '../utils/genJsonPayload';
const checkUsernameEventHandler = _e => {
checkUsernameExists();
};
//export const checkUsernameExists = async () => {
async function checkUsernameExists() {
let username = document.getElementById('username');
let val = username.value;
let payload = {
val,
};
// return fetch(ROUTES.usernameExists, genJsonPayload(payload)).then(res => {
// if (res.ok) {
// res.json().then(data => {
// if (data.exists) {
// username.className += ' form__in-field--warn';
// alert('Username taken');
// }
// return data.exists;
// });
// } else {
// res.json().then(err => alert(`error: ${err.error}`));
// }
// });
//
let res = await fetch(ROUTES.usernameExists, genJsonPayload(payload));
if (res.ok) {
let data = await res.json();
if (data.exists) {
username.className += ' form__in-field--warn';
alert('Username taken');
}
return data.exists;
} else {
let err = await res.json();
alert(`error: ${err.error}`);
}
return false;
};
export {checkUsernameExists, checkUsernameEventHandler};

104
frontend/src/css/forms.scss Normal file
View file

@ -0,0 +1,104 @@
* {
padding: 0;
margin: 0;
}
.form__logo {
width: 110px;
padding-top: 50px;
display: block;
margin: auto;
position: relative;
top: 20%;
transform: translate(0%, -40.9%);
}
.form__brand {
padding: 10px 0;
text-align: center;
position: relative;
top: 20%;
transform: translate(0%, -90.9%);
}
.form-container {
max-width: 40%;
min-width: 20%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -49.9%);
box-sizing: border-box;
margin: auto;
padding: 20px 0;
}
.form__box {
border: 1px solid #eaecef;
background-color: #f6f8fa;
border-radius: 5px;
padding: 20px 0;
}
.form__in-group {
display: block;
position: relative;
margin: auto;
max-width: 80%;
padding: 10px 0px;
box-sizing: content-box;
align-items: center;
align-content: center;
}
.form__in-field {
display: block;
box-sizing: border-box;
margin: 10px 0;
padding: 10px 0;
width: 100%;
}
.form__in-field--warn {
border: solid 1px red;
}
.form__in-field--success {
border: solid 1px #2ea44f;
}
.form__pw-recovery {
text-decoration: none;
color: rgb(3, 102, 214);
font-size: 0.8rem;
}
.form__submit-button {
display: block;
border: 1px solid skyblue;
background: #2ea44f;
color: white;
height: 40px;
border-radius: 5px;
width: 80%;
margin: auto;
}
.form__secondary-action {
display: block;
margin-top: 10px;
}
.form__secondary-action__banner {
display: block;
margin: auto;
max-width: 80%;
text-align: center;
}
.form__secondary-action__link {
text-decoration: none;
color: rgb(3, 102, 214);
}

19
frontend/src/index.js Normal file
View file

@ -0,0 +1,19 @@
import './css/forms.scss';
import signin from './auth/signin';
import registerUser from './auth/register';
import {checkUsernameEventHandler} from './auth/userExists';
if (window.location.pathname == '/') {
let form = document.getElementById('form');
form.addEventListener('submit', signin, true);
} else if (window.location.pathname == '/signup') {
let form = document.getElementById('form');
form.addEventListener('submit', registerUser, true);
let username = document.getElementById('username');
username.addEventListener('input', checkUsernameEventHandler, false);
} else {
}
//export default signin;

View file

@ -0,0 +1,8 @@
import './css/forms.scss';
import registerUser from './auth/register';
let form = document.getElementById('form');
form.addEventListener('submit', registerUser, true);
let username = document.getElementById('username');
username.addEventListener('input', checkUsernameEventHandler, false);

View file

@ -0,0 +1,12 @@
const genJsonPayload = payload => {
let value = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
};
return value;
};
export default genJsonPayload;

View file

@ -0,0 +1,8 @@
const isBlankString = (event, value, field) => {
if (!value.replace(/\s/g, '').length) {
event.preventDefault();
alert(`${field} can't be empty`);
}
};
export default isBlankString;

View file

@ -0,0 +1,25 @@
const path = require('path');
module.exports = {
entry: {
main: './src/index.js',
},
module: {
rules: [
{
test: /\.html$/,
use: ['html-loader'],
},
{
test: /\.(svg|png|jpg|gif)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[hash].[ext]',
outputPath: 'imgs',
},
},
},
],
},
};

34
frontend/webpack.dev.js Normal file
View file

@ -0,0 +1,34 @@
const path = require("path");
const common = require("./webpack.common");
const merge = require("webpack-merge");
var HtmlWebpackPlugin = require("html-webpack-plugin");
module.exports = merge(common, {
mode: "development",
output: {
filename: "[name].bundle.js",
path: path.resolve(__dirname, "dist")
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.html"
}),
new HtmlWebpackPlugin({
filename: "signup/index.html",
template: "./public/signup/index.html"
})
],
module: {
rules: [
{
test: /\.scss$/,
use: [
"style-loader", //3. Inject styles into DOM
"css-loader", //2. Turns css into commonjs
"sass-loader" //1. Turns sass into css
]
}
]
}
});

55
frontend/webpack.prod.js Normal file
View file

@ -0,0 +1,55 @@
const path = require('path');
const common = require('./webpack.common');
const merge = require('webpack-merge');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
var HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = merge(common, {
mode: 'production',
output: {
filename: '[name].[contentHash].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
optimization: {
minimizer: [
new OptimizeCssAssetsPlugin(),
new TerserPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public/', 'index.html'),
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true,
removeComments: true,
},
}),
new HtmlWebpackPlugin({
filename: 'signup/index.html',
template: path.resolve(__dirname, 'public/signup/', 'index.html'),
minify: {
removeAttributeQuotes: true,
collapseWhitespace: true,
removeComments: true,
},
}),
],
},
plugins: [
new MiniCssExtractPlugin({filename: '[name].[contentHash].css'}),
new CleanWebpackPlugin(),
],
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader, //3. Extract css into files
'css-loader', //2. Turns css into commonjs
'sass-loader', //1. Turns sass into css
],
},
],
},
});

2756
frontend/yarn-error.log Normal file

File diff suppressed because it is too large Load diff

5932
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load diff

View file

@ -42,11 +42,14 @@ pub use settings::Settings;
lazy_static! {
pub static ref SETTINGS: Settings = Settings::new().unwrap();
pub static ref GIT_COMMIT_HASH: String = env::var("GIT_HASH").unwrap();
pub static ref OPEN_API_DOC: String = env::var("OPEN_API_DOCS").unwrap();
// pub static ref GIT_COMMIT_HASH: String = env::var("GIT_HASH").unwrap();
// pub static ref OPEN_API_DOC: String = env::var("OPEN_API_DOCS").unwrap();
pub static ref S: String = env::var("S").unwrap();
}
pub static OPEN_API_DOC: &str = env!("OPEN_API_DOCS");
pub static GIT_COMMIT_HASH: &str = env!("GIT_HASH");
pub static VERSION: &str = env!("CARGO_PKG_VERSION");
pub static PKG_NAME: &str = env!("CARGO_PKG_NAME");
pub static PKG_DESCRIPTION: &str = env!("CARGO_PKG_DESCRIPTION");
@ -62,7 +65,7 @@ async fn main() -> std::io::Result<()> {
pretty_env_logger::init();
info!(
"{}: {}.\nFor more information, see: {}\nBuild info:\nVersion: {} commit: {}",
PKG_NAME, PKG_DESCRIPTION, PKG_HOMEPAGE, VERSION, *GIT_COMMIT_HASH
PKG_NAME, PKG_DESCRIPTION, PKG_HOMEPAGE, VERSION, GIT_COMMIT_HASH
);
let data = Data::new().await;
@ -82,7 +85,7 @@ async fn main() -> std::io::Result<()> {
.configure(v1::services)
.configure(docs::services)
.app_data(get_json_err())
.service(Files::new("/", "./static"))
.service(Files::new("/", "./frontend/dist").index_file("index.html"))
})
.bind(SETTINGS.server.get_ip())
.unwrap()

View file

@ -1,193 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Login | mCaptcha</title>
</head>
<body>
<img src="/img/icon-trans.png" class="form__logo" alt="" />
<h2 class="form__brand">Sign in to mCaptcha</h2>
<div class="form-container">
<form class="form__box">
<label class="form__in-group" for="username"
>Username
<input
class="form__in-field"
id="username"
type="text"
name="username"
id="username"
required
/>
</label>
<label for="password" class="form__in-group"
>Password
<input
class="form__in-field"
type="password"
id="password"
name="password"
id="password"
required
/>
<!--
<a class="form__pw-recovery" -href="/recovert/password"
>Forgot password?</a
>
-->
</label>
<button class="form__submit-button" type="submit">
Submit
</button>
</form>
<div class="form__secondary-action">
<p class="form__secondary-action__banner">New to mCaptcha? <a href="/signup" class="form__secondary-action__link">Create account</a></p>
</div>
</div>
<script>
export const SUBMIT = '/api/v1/signup';
export const isBlankString = (event, value, field) => {
if (!value.replace(/\s/g, '').length) {
event.preventDefautl()
alert(`${field} can't be empty`);
}
};
const genJsonPayload = payload => {
let value = {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(payload),
};
return value;
};
const submit = async e => {
let name = document.getElementById('username').value;
isBlankString(e, name, 'username');
let password = document.getElementById('password').value;
isBlankString(e, email, 'email');
let payload = {
username,
password
}
fetch(SUBMIT, genJsonPayload(payload)).then(resp => {
if (resp.ok) {
aslert('signed in');
} else {
resp.json().then(err => alert(`Error ${resp.error}`));
}
});
};
let form = document.getElementById('form');
form.addEventListener('submit', submit, true);
</script>
</body>
<style>
* {
padding: 0;
margin: 0;
}
.form__logo {
width: 120px;
padding-top: 50px;
display: block;
margin: auto;
}
.form__brand {
padding: 10px 0;
text-align: center;
}
.form-container {
max-width: 40%;
min-width: 20%;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -49.9%);
box-sizing: border-box;
margin: auto;
padding: 20px 0;
}
.form__box {
border: 1px solid #eaecef;
background-color: #f6f8fa;
border-radius: 5px;
padding: 20px 0;
}
.form__in-group {
display: block;
position: relative;
margin: auto;
max-width: 80%;
padding: 10px 0px;
box-sizing: content-box;
align-items: center;
align-content: center;
}
.form__in-field {
display: block;
box-sizing: border-box;
margin: 10px 0;
padding: 10px 0;
width: 100%;
}
.form__pw-recovery {
text-decoration: none;
color: rgb(3, 102, 214);
font-size: 0.8rem;
}
.form__submit-button {
display: block;
border: 1px solid skyblue;
background: #2ea44f;
color: white;
height: 40px;
border-radius: 5px;
width: 80%;
margin: auto;
}
.form__secondary-action {
display: block;
margin-top: 10px;
}
.form__secondary-action__banner {
display: block;
margin: auto;
max-width: 80%;
text-align: center;
}
.form__secondary-action__link{
text-decoration: none;
color: rgb(3, 102, 214);
}
</style>
</html>