Add new profile endpoint
Add some logging output for auth failures
This commit is contained in:
parent
0453e461c9
commit
1e2bb83447
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@hibas123/openauth-backend",
|
"name": "@hibas123/openauth-backend",
|
||||||
"version": "1.1.2",
|
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -5,6 +5,7 @@ import { validateJWT } from "../../keys";
|
|||||||
import User from "../../models/user";
|
import User from "../../models/user";
|
||||||
import Mail from "../../models/mail";
|
import Mail from "../../models/mail";
|
||||||
import { OAuthJWT } from "../../helper/jwt";
|
import { OAuthJWT } from "../../helper/jwt";
|
||||||
|
import Logging from "@hibas123/nodelogging";
|
||||||
|
|
||||||
export function GetClientAuthMiddleware(
|
export function GetClientAuthMiddleware(
|
||||||
checksecret = true,
|
checksecret = true,
|
||||||
@ -67,13 +68,16 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) {
|
|||||||
return async (req: Request, res: Response, next: NextFunction) => {
|
return async (req: Request, res: Response, next: NextFunction) => {
|
||||||
try {
|
try {
|
||||||
const invalid_err = new RequestError(
|
const invalid_err = new RequestError(
|
||||||
req.__("You are not logged in or your login is expired"),
|
req.__("Unauthorized"),
|
||||||
HttpStatusCode.UNAUTHORIZED
|
HttpStatusCode.UNAUTHORIZED
|
||||||
);
|
);
|
||||||
let token =
|
let token =
|
||||||
(req.query.access_token as string) ||
|
(req.query.access_token as string) ||
|
||||||
(req.headers.authorization as string);
|
(req.headers.authorization as string);
|
||||||
if (!token) throw invalid_err;
|
if (!token) {
|
||||||
|
Logging.debug("No token found. Searched in query (access_token) and header (authorization)");
|
||||||
|
throw invalid_err;
|
||||||
|
}
|
||||||
|
|
||||||
if (token.toLowerCase().startsWith("bearer "))
|
if (token.toLowerCase().startsWith("bearer "))
|
||||||
token = token.substring(7);
|
token = token.substring(7);
|
||||||
@ -82,22 +86,31 @@ export function GetClientApiAuthMiddleware(permissions?: string[]) {
|
|||||||
try {
|
try {
|
||||||
data = await validateJWT(token);
|
data = await validateJWT(token);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
Logging.debug("Invalid JWT", err.message);
|
||||||
throw invalid_err;
|
throw invalid_err;
|
||||||
}
|
}
|
||||||
|
|
||||||
let user = await User.findOne({ uid: data.user });
|
let user = await User.findOne({ uid: data.user });
|
||||||
|
|
||||||
if (!user) throw invalid_err;
|
if (!user) {
|
||||||
|
Logging.debug("User not found");
|
||||||
|
throw invalid_err;
|
||||||
|
}
|
||||||
|
|
||||||
let client = await Client.findOne({ client_id: data.application });
|
let client = await Client.findOne({ client_id: data.application });
|
||||||
if (!client) throw invalid_err;
|
if (!client) {
|
||||||
|
Logging.debug("Client not found");
|
||||||
|
throw invalid_err;
|
||||||
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
permissions &&
|
permissions &&
|
||||||
(!data.permissions ||
|
(!data.permissions ||
|
||||||
!permissions.every((e) => data.permissions.indexOf(e) >= 0))
|
!permissions.every((e) => data.permissions.indexOf(e) >= 0))
|
||||||
)
|
) {
|
||||||
|
Logging.debug("Invalid permissions");
|
||||||
throw invalid_err;
|
throw invalid_err;
|
||||||
|
}
|
||||||
|
|
||||||
req.user = user;
|
req.user = user;
|
||||||
req.client = client;
|
req.client = client;
|
||||||
|
@ -3,8 +3,9 @@ import GetAuthRoute from "./auth";
|
|||||||
import JWTRoute from "./jwt";
|
import JWTRoute from "./jwt";
|
||||||
import Public from "./public";
|
import Public from "./public";
|
||||||
import RefreshTokenRoute from "./refresh";
|
import RefreshTokenRoute from "./refresh";
|
||||||
|
import ProfileRoute from "./profile";
|
||||||
|
|
||||||
const OAuthRoue: Router = Router();
|
const OAuthRoute: Router = Router();
|
||||||
/**
|
/**
|
||||||
* @api {post} /oauth/auth
|
* @api {post} /oauth/auth
|
||||||
* @apiName OAuthAuth
|
* @apiName OAuthAuth
|
||||||
@ -19,7 +20,7 @@ const OAuthRoue: Router = Router();
|
|||||||
* @apiParam {String} state State, that will be passed to redirect_uri for client
|
* @apiParam {String} state State, that will be passed to redirect_uri for client
|
||||||
* @apiParam {String} nored Deactivates the Redirect response from server and instead returns the redirect URI in JSON response
|
* @apiParam {String} nored Deactivates the Redirect response from server and instead returns the redirect URI in JSON response
|
||||||
*/
|
*/
|
||||||
OAuthRoue.post("/auth", GetAuthRoute(false));
|
OAuthRoute.post("/auth", GetAuthRoute(false));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} /oauth/jwt
|
* @api {get} /oauth/jwt
|
||||||
@ -32,7 +33,7 @@ OAuthRoue.post("/auth", GetAuthRoute(false));
|
|||||||
*
|
*
|
||||||
* @apiSuccess {String} token The JWT that allowes the application to access the recources granted for refresh token
|
* @apiSuccess {String} token The JWT that allowes the application to access the recources granted for refresh token
|
||||||
*/
|
*/
|
||||||
OAuthRoue.get("/jwt", JWTRoute);
|
OAuthRoute.get("/jwt", JWTRoute);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} /oauth/public
|
* @api {get} /oauth/public
|
||||||
@ -43,7 +44,7 @@ OAuthRoue.get("/jwt", JWTRoute);
|
|||||||
*
|
*
|
||||||
* @apiSuccess {String} public_key The applications public_key. Used to verify JWT.
|
* @apiSuccess {String} public_key The applications public_key. Used to verify JWT.
|
||||||
*/
|
*/
|
||||||
OAuthRoue.get("/public", Public);
|
OAuthRoute.get("/public", Public);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {get} /oauth/refresh
|
* @api {get} /oauth/refresh
|
||||||
@ -51,7 +52,7 @@ OAuthRoue.get("/public", Public);
|
|||||||
*
|
*
|
||||||
* @apiGroup oauth
|
* @apiGroup oauth
|
||||||
*/
|
*/
|
||||||
OAuthRoue.get("/refresh", RefreshTokenRoute);
|
OAuthRoute.get("/refresh", RefreshTokenRoute);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {post} /oauth/refresh
|
* @api {post} /oauth/refresh
|
||||||
@ -59,5 +60,14 @@ OAuthRoue.get("/refresh", RefreshTokenRoute);
|
|||||||
*
|
*
|
||||||
* @apiGroup oauth
|
* @apiGroup oauth
|
||||||
*/
|
*/
|
||||||
OAuthRoue.post("/refresh", RefreshTokenRoute);
|
OAuthRoute.post("/refresh", RefreshTokenRoute);
|
||||||
export default OAuthRoue;
|
|
||||||
|
/**
|
||||||
|
* @api {get} /oauth/profile
|
||||||
|
* @apiName OAuthProfile
|
||||||
|
*
|
||||||
|
* @apiGroup oauth
|
||||||
|
*/
|
||||||
|
OAuthRoute.get("/profile", ProfileRoute);
|
||||||
|
|
||||||
|
export default OAuthRoute;
|
||||||
|
23
Backend/src/api/oauth/profile.ts
Normal file
23
Backend/src/api/oauth/profile.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import Mail from "../../models/mail";
|
||||||
|
import { GetClientApiAuthMiddleware } from "../middlewares/client";
|
||||||
|
import Stacker from "../middlewares/stacker";
|
||||||
|
import { Request, Response } from "express";
|
||||||
|
|
||||||
|
export default Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) => {
|
||||||
|
let mails = await Promise.all(
|
||||||
|
req.user.mails.map((id) => Mail.findById(id))
|
||||||
|
);
|
||||||
|
|
||||||
|
let mail = mails.find((e) => e.primary) || mails[0];
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
user_id: req.user.uid,
|
||||||
|
id: req.user.uid,
|
||||||
|
ID: req.user.uid,
|
||||||
|
sub: req.user.uid,
|
||||||
|
email: mail.mail,
|
||||||
|
username: req.user.username,
|
||||||
|
displayName: req.user.name,
|
||||||
|
displayNameClaim: req.user.name,
|
||||||
|
});
|
||||||
|
})
|
@ -101,7 +101,7 @@ export default async function TestData() {
|
|||||||
data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ",
|
data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ",
|
||||||
expires: null,
|
expires: null,
|
||||||
});
|
});
|
||||||
TwoFactor.save(t);
|
await TwoFactor.save(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
let login_token = await LoginToken.findOne({ token: "test01" });
|
let login_token = await LoginToken.findOne({ token: "test01" });
|
||||||
|
@ -63,8 +63,7 @@ export default class Web {
|
|||||||
let m = req.method;
|
let m = req.method;
|
||||||
while (m.length < 4) m += " ";
|
while (m.length < 4) m += " ";
|
||||||
Logging.log(
|
Logging.log(
|
||||||
`${m} ${req.originalUrl} ${
|
`${m} ${req.originalUrl} ${(req as any).language || ""
|
||||||
(req as any).language || ""
|
|
||||||
} ${resColor}${res.statusCode}\x1b[0m - ${time}ms`
|
} ${resColor}${res.statusCode}\x1b[0m - ${time}ms`
|
||||||
);
|
);
|
||||||
res.removeListener("finish", listener);
|
res.removeListener("finish", listener);
|
||||||
@ -107,7 +106,7 @@ export default class Web {
|
|||||||
if (error.status === 500 && !(<any>error).nolog) {
|
if (error.status === 500 && !(<any>error).nolog) {
|
||||||
Logging.error(error);
|
Logging.error(error);
|
||||||
} else {
|
} else {
|
||||||
Logging.log("Responded with Error", error.status);
|
Logging.log("Responded with Error", error.status, error.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (req.accepts(["json"])) {
|
if (req.accepts(["json"])) {
|
||||||
|
11
Frontend/.editorconfig
Normal file
11
Frontend/.editorconfig
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 3
|
||||||
|
charset = utf-8
|
||||||
|
trim_trailing_whitespace = false
|
||||||
|
insert_final_newline = true
|
||||||
|
|
||||||
|
[*.svelte]
|
||||||
|
indent_size = 2
|
8
Frontend/.gitignore
vendored
Normal file
8
Frontend/.gitignore
vendored
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.DS_Store
|
||||||
|
node_modules
|
||||||
|
public/bundle.*
|
||||||
|
yarn.lock
|
||||||
|
.rpt2_cache
|
||||||
|
build/
|
||||||
|
build.js
|
||||||
|
*.old
|
68
Frontend/README.md
Normal file
68
Frontend/README.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
_Psst <14>looking for a shareable component template? Go here --> [sveltejs/component-template](https://github.com/sveltejs/component-template)_
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# svelte app
|
||||||
|
|
||||||
|
This is a project template for [Svelte](https://svelte.technology) apps. It lives at https://github.com/sveltejs/template.
|
||||||
|
|
||||||
|
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g degit # you only need to do this once
|
||||||
|
|
||||||
|
degit sveltejs/template svelte-app
|
||||||
|
cd svelte-app
|
||||||
|
```
|
||||||
|
|
||||||
|
_Note that you will need to have [Node.js](https://nodejs.org) installed._
|
||||||
|
|
||||||
|
## Get started
|
||||||
|
|
||||||
|
Install the dependencies...
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd svelte-app
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
...then start [Rollup](https://rollupjs.org):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
Navigate to [localhost:5000](http://localhost:5000). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
|
||||||
|
|
||||||
|
## Deploying to the web
|
||||||
|
|
||||||
|
### With [now](https://zeit.co/now)
|
||||||
|
|
||||||
|
Install `now` if you haven't already:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g now
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, from within your project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
now
|
||||||
|
```
|
||||||
|
|
||||||
|
As an alternative, use the [Now desktop client](https://zeit.co/download) and simply drag the unzipped project folder to the taskbar icon.
|
||||||
|
|
||||||
|
### With [surge](https://surge.sh/)
|
||||||
|
|
||||||
|
Install `surge` if you haven't already:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install -g surge
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, from within your project folder:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run build
|
||||||
|
surge public
|
||||||
|
```
|
0
Frontend/index.js
Normal file
0
Frontend/index.js
Normal file
36
Frontend/package.json
Normal file
36
Frontend/package.json
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"name": "@hibas123/openauth-views-v2",
|
||||||
|
"main": "index.js",
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-html": "^1.0.2",
|
||||||
|
"@rollup/plugin-image": "^3.0.2",
|
||||||
|
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||||
|
"@tsconfig/svelte": "^4.0.1",
|
||||||
|
"@types/cleave.js": "^1.4.7",
|
||||||
|
"esbuild": "^0.17.15",
|
||||||
|
"postcss": "^8.4.21",
|
||||||
|
"postcss-import": "^15.1.0",
|
||||||
|
"postcss-url": "^10.1.3",
|
||||||
|
"rollup": "^3.20.2",
|
||||||
|
"rollup-plugin-esbuild": "^5.0.0",
|
||||||
|
"rollup-plugin-livereload": "^2.0.5",
|
||||||
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
|
"rollup-plugin-sizes": "^1.0.5",
|
||||||
|
"rollup-plugin-svelte": "^7.1.4",
|
||||||
|
"rollup-plugin-visualizer": "^5.9.0",
|
||||||
|
"svelte": "^3.58.0",
|
||||||
|
"svelte-preprocess": "^5.0.3",
|
||||||
|
"typescript": "^5.0.3"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"prepublishOnly": "npm run build",
|
||||||
|
"build": "rollup -c rollup.config.mjs ",
|
||||||
|
"dev": "rollup -c rollup.config.mjs -w"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@hibas123/theme": "^2.0.6",
|
||||||
|
"@hibas123/utils": "^2.2.18",
|
||||||
|
"cleave.js": "^1.6.0",
|
||||||
|
"what-the-pack": "^2.0.3"
|
||||||
|
}
|
||||||
|
}
|
3
Frontend/postcss.config.js
Normal file
3
Frontend/postcss.config.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module.exports = {
|
||||||
|
plugins: [],
|
||||||
|
};
|
123
Frontend/rollup.config.mjs
Normal file
123
Frontend/rollup.config.mjs
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import svelte from "rollup-plugin-svelte";
|
||||||
|
import esbuild from "rollup-plugin-esbuild";
|
||||||
|
import html from "@rollup/plugin-html";
|
||||||
|
import resolve from "@rollup/plugin-node-resolve";
|
||||||
|
import image from "@rollup/plugin-image";
|
||||||
|
import sizes from "rollup-plugin-sizes";
|
||||||
|
import { visualizer } from "rollup-plugin-visualizer";
|
||||||
|
import postcss from "rollup-plugin-postcss";
|
||||||
|
import livereload from "rollup-plugin-livereload";
|
||||||
|
import sveltePreprocess from "svelte-preprocess";
|
||||||
|
|
||||||
|
const VIEWS = ["home", "login", "popup", "user"];
|
||||||
|
|
||||||
|
const dev = process.env.NODE_ENV !== "production";
|
||||||
|
|
||||||
|
const htmlTemplate = ({ attributes, meta, files, publicPath, title }) => {
|
||||||
|
const makeHtmlAttributes = (attributes) => {
|
||||||
|
if (!attributes) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const keys = Object.keys(attributes);
|
||||||
|
// eslint-disable-next-line no-param-reassign
|
||||||
|
return keys.reduce(
|
||||||
|
(result, key) => (result += ` ${key}="${attributes[key]}"`),
|
||||||
|
""
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const scripts = (files.js || [])
|
||||||
|
.map(({ fileName }) => {
|
||||||
|
const attrs = makeHtmlAttributes(attributes.script);
|
||||||
|
return `<script src="${publicPath}${fileName}"${attrs}></script>`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
const links = (files.css || [])
|
||||||
|
.map(({ fileName }) => {
|
||||||
|
const attrs = makeHtmlAttributes(attributes.link);
|
||||||
|
return `<link href="${publicPath}${fileName}" rel="stylesheet"${attrs}>`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
const metas = meta
|
||||||
|
.map((input) => {
|
||||||
|
const attrs = makeHtmlAttributes(input);
|
||||||
|
return `<meta${attrs}>`;
|
||||||
|
})
|
||||||
|
.join("\n");
|
||||||
|
|
||||||
|
return `
|
||||||
|
<!doctype html>
|
||||||
|
<html${makeHtmlAttributes(attributes.html)}>
|
||||||
|
<head>
|
||||||
|
${metas}
|
||||||
|
<title>${title}</title>
|
||||||
|
<link rel="stylesheet" href="bundle.css"/>
|
||||||
|
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto"/>
|
||||||
|
${links}
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
${scripts}
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default VIEWS.map((view) => ({
|
||||||
|
input: `src/pages/${view}/main.ts`,
|
||||||
|
output: [
|
||||||
|
dev
|
||||||
|
? {
|
||||||
|
file: `build/${view}/bundle.js`,
|
||||||
|
format: "iife",
|
||||||
|
sourcemap: true,
|
||||||
|
name: view,
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
file: `build/${view}/bundle.min.js`,
|
||||||
|
format: "iife",
|
||||||
|
name: view,
|
||||||
|
plugins: [
|
||||||
|
esbuild({
|
||||||
|
minify: true,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
svelte({
|
||||||
|
emitCss: true,
|
||||||
|
preprocess: sveltePreprocess({}),
|
||||||
|
}),
|
||||||
|
esbuild({ sourceMap: dev }),
|
||||||
|
html({
|
||||||
|
title: view,
|
||||||
|
attributes: {
|
||||||
|
html: { lang: "en" },
|
||||||
|
},
|
||||||
|
meta: [
|
||||||
|
{
|
||||||
|
name: "viewport",
|
||||||
|
content: "width=device-width",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
template: htmlTemplate,
|
||||||
|
}),
|
||||||
|
resolve({
|
||||||
|
browser: true,
|
||||||
|
dedupe: ["svelte"],
|
||||||
|
exportConditions: ["svelte"],
|
||||||
|
}),
|
||||||
|
image(),
|
||||||
|
sizes(),
|
||||||
|
visualizer({
|
||||||
|
filename: `build/stats/${view}.html`,
|
||||||
|
title: `Rullup bundle for ${view}`,
|
||||||
|
}),
|
||||||
|
postcss({
|
||||||
|
extract: `bundle.css`, //TODO: Check if it should be enabled
|
||||||
|
// inject: true,
|
||||||
|
}),
|
||||||
|
// dev && livereload(),
|
||||||
|
],
|
||||||
|
}));
|
90
Frontend/src/components/HoveringContentBox.svelte
Normal file
90
Frontend/src/components/HoveringContentBox.svelte
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
// import { Tile } from "carbon-components-svelte";
|
||||||
|
|
||||||
|
export let title: string;
|
||||||
|
export let loading = false;
|
||||||
|
export let hide = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.wrapper {
|
||||||
|
min-height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
border-radius: 4px;
|
||||||
|
position: relative;
|
||||||
|
padding-top: 2.5rem;
|
||||||
|
|
||||||
|
min-height: calc(100px + 2.5rem);
|
||||||
|
min-width: 100px;
|
||||||
|
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-container {
|
||||||
|
margin: -4.8rem auto 0 auto;
|
||||||
|
max-width: 250px;
|
||||||
|
background-color: var(--primary);
|
||||||
|
color: white;
|
||||||
|
border-radius: 4px;
|
||||||
|
/* padding: 5px 20px; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.title-container > h1 {
|
||||||
|
font-size: 2rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
padding: 2em;
|
||||||
|
margin: 0 auto;
|
||||||
|
max-width: 380px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading_container {
|
||||||
|
filter: blur(1px) opacity(50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader_container {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="wrapper">
|
||||||
|
<div class="card-elevated container">
|
||||||
|
<!-- <div class="container card"> -->
|
||||||
|
<div class="card elv-8 title-container">
|
||||||
|
<h1 style="margin:0">{title}</h1>
|
||||||
|
</div>
|
||||||
|
{#if loading}
|
||||||
|
<div class="loader_container">
|
||||||
|
<div class="loader_box">
|
||||||
|
<div class="loader" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="content-container" class:loading_container={loading}>
|
||||||
|
{#if !(loading && hide)}
|
||||||
|
<slot />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
<!-- </div> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
15
Frontend/src/components/theme/Theme.svelte
Normal file
15
Frontend/src/components/theme/Theme.svelte
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
// import { onMount, afterUpdate, setContext } from "svelte";
|
||||||
|
// import { writable, derived } from "svelte/store";
|
||||||
|
|
||||||
|
// type Theme = "white" | "g10" | "g90" | "g100";
|
||||||
|
|
||||||
|
// export let persist: boolean = false;
|
||||||
|
// export let persistKey: string = "theme";
|
||||||
|
export let dark = false;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
<div class={dark ? 'dark-theme' : 'light-theme'}>
|
||||||
|
<slot />
|
||||||
|
</div>
|
42
Frontend/src/components/theme/index.ts
Normal file
42
Frontend/src/components/theme/index.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import "@hibas123/theme/out/base.css";
|
||||||
|
import "./theme.css";
|
||||||
|
import { default as Theme } from "./Theme.svelte";
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
const elements = new WeakSet();
|
||||||
|
|
||||||
|
function check() {
|
||||||
|
document
|
||||||
|
.querySelectorAll(".floating>input")
|
||||||
|
.forEach((e: HTMLInputElement) => {
|
||||||
|
if (elements.has(e)) return;
|
||||||
|
elements.add(e);
|
||||||
|
|
||||||
|
function checkState() {
|
||||||
|
console.log("Check State");
|
||||||
|
if (e.value !== "") {
|
||||||
|
if (e.classList.contains("used")) return;
|
||||||
|
e.classList.add("used");
|
||||||
|
} else {
|
||||||
|
if (e.classList.contains("used")) e.classList.remove("used");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.addEventListener("change", () => checkState());
|
||||||
|
checkState();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
check();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start observing the target node for configured mutations
|
||||||
|
observer.observe(window.document, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
});
|
||||||
|
check();
|
||||||
|
})();
|
||||||
|
|
||||||
|
export default Theme;
|
251
Frontend/src/components/theme/theme.css
Normal file
251
Frontend/src/components/theme/theme.css
Normal file
@ -0,0 +1,251 @@
|
|||||||
|
:root {
|
||||||
|
--primary: #1e88e5;
|
||||||
|
--mdc-theme-primary: var(--primary);
|
||||||
|
--mdc-theme-primary-bg: var(--mdc-theme--primary);
|
||||||
|
--mdc-theme-on-primary: white;
|
||||||
|
--error: #ff2f00;
|
||||||
|
--border-color: #ababab;
|
||||||
|
|
||||||
|
--default-font-size: 1.05rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
font-family: "Roboto", "Helvetica", sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
color: #636363;
|
||||||
|
position: relative;
|
||||||
|
background: #eee;
|
||||||
|
height: 100%;
|
||||||
|
font-size: var(--default-font-size);
|
||||||
|
min-width: 100vw;
|
||||||
|
min-height: 100vh;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.group {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
min-height: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating > input {
|
||||||
|
font-size: 1.2rem;
|
||||||
|
padding: 10px 10px 10px 5px;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
display: block;
|
||||||
|
background: #fafafa;
|
||||||
|
background: unset;
|
||||||
|
color: #636363;
|
||||||
|
width: 100%;
|
||||||
|
border: none;
|
||||||
|
border-radius: 0;
|
||||||
|
/* border-bottom: 1px solid #757575; */
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating > input:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Label */
|
||||||
|
|
||||||
|
.floating > label {
|
||||||
|
color: #999;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: normal;
|
||||||
|
position: absolute;
|
||||||
|
pointer-events: none;
|
||||||
|
left: 5px;
|
||||||
|
top: 10px;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* active */
|
||||||
|
|
||||||
|
.floating > input:focus ~ label,
|
||||||
|
.floating > input.used ~ label {
|
||||||
|
top: -0.75em;
|
||||||
|
transform: scale(0.75);
|
||||||
|
left: -2px;
|
||||||
|
/* font-size: 14px; */
|
||||||
|
color: var(--primary);
|
||||||
|
transform-origin: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Underline */
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:before,
|
||||||
|
.bar:after {
|
||||||
|
content: "";
|
||||||
|
height: 2px;
|
||||||
|
width: 0;
|
||||||
|
bottom: 1px;
|
||||||
|
position: absolute;
|
||||||
|
background: var(--primary);
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:before {
|
||||||
|
left: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:after {
|
||||||
|
right: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* active */
|
||||||
|
|
||||||
|
.floating > input:focus ~ .bar:before,
|
||||||
|
.floating > input:focus ~ .bar:after {
|
||||||
|
width: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Highlight */
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
position: absolute;
|
||||||
|
height: 60%;
|
||||||
|
width: 100px;
|
||||||
|
top: 25%;
|
||||||
|
left: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* active */
|
||||||
|
|
||||||
|
.floating > input:focus ~ .highlight {
|
||||||
|
animation: inputHighlighter 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Animations */
|
||||||
|
|
||||||
|
@keyframes inputHighlighter {
|
||||||
|
from {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
width: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
margin: 2rem;
|
||||||
|
padding: 0 1em;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
border-width: 0;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.6);
|
||||||
|
|
||||||
|
background-color: #cccccc;
|
||||||
|
color: #ecf0f1;
|
||||||
|
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
|
||||||
|
height: 48px;
|
||||||
|
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover,
|
||||||
|
.btn:focus {
|
||||||
|
filter: brightness(90%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn > * {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn span {
|
||||||
|
display: block;
|
||||||
|
padding: 12px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:before {
|
||||||
|
content: "";
|
||||||
|
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
|
||||||
|
display: block;
|
||||||
|
width: 0;
|
||||||
|
padding-top: 0;
|
||||||
|
|
||||||
|
border-radius: 100%;
|
||||||
|
|
||||||
|
background-color: rgba(236, 240, 241, 0.3);
|
||||||
|
|
||||||
|
-webkit-transform: translate(-50%, -50%);
|
||||||
|
-moz-transform: translate(-50%, -50%);
|
||||||
|
-ms-transform: translate(-50%, -50%);
|
||||||
|
-o-transform: translate(-50%, -50%);
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:active:before {
|
||||||
|
width: 120%;
|
||||||
|
padding-top: 120%;
|
||||||
|
|
||||||
|
transition: width 0.2s ease-out, padding-top 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader_box {
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader {
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
z-index: 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader:after {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
width: 46px;
|
||||||
|
height: 46px;
|
||||||
|
margin: 1px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 5px solid var(--primary);
|
||||||
|
border-color: var(--primary) transparent var(--primary) transparent;
|
||||||
|
animation: loader 1.2s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes loader {
|
||||||
|
0% {
|
||||||
|
transform: rotate(0deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
20
Frontend/src/helper/cookie.ts
Normal file
20
Frontend/src/helper/cookie.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export function setCookie(cname: string, cvalue: string, exdate: string) {
|
||||||
|
const expires = exdate ? `;expires=${exdate}` : "";
|
||||||
|
document.cookie = `${cname}=${cvalue}${expires};path=/;`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCookie(cname: string) {
|
||||||
|
const name = cname + "=";
|
||||||
|
const dc = decodeURIComponent(document.cookie);
|
||||||
|
const ca = dc.split(";");
|
||||||
|
for (let i = 0; i < ca.length; i++) {
|
||||||
|
let c = ca[i];
|
||||||
|
while (c.charAt(0) == " ") {
|
||||||
|
c = c.substring(1);
|
||||||
|
}
|
||||||
|
if (c.indexOf(name) == 0) {
|
||||||
|
return c.substring(name.length, c.length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
53
Frontend/src/helper/request.ts
Normal file
53
Frontend/src/helper/request.ts
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { getCookie } from "./cookie";
|
||||||
|
|
||||||
|
const baseURL = "";
|
||||||
|
|
||||||
|
export default async function request(
|
||||||
|
endpoint: string,
|
||||||
|
parameters: { [key: string]: string } = {},
|
||||||
|
method: "GET" | "POST" | "DELETE" | "PUT" = "GET",
|
||||||
|
body?: any,
|
||||||
|
authInParam = false,
|
||||||
|
redirect = false
|
||||||
|
) {
|
||||||
|
let pairs = [];
|
||||||
|
|
||||||
|
if (authInParam) {
|
||||||
|
parameters.login = getCookie("login");
|
||||||
|
parameters.special = getCookie("special");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let key in parameters) {
|
||||||
|
pairs.push(key + "=" + parameters[key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = endpoint;
|
||||||
|
if (pairs.length > 0) {
|
||||||
|
url += "?" + pairs.join("&");
|
||||||
|
}
|
||||||
|
|
||||||
|
return fetch(baseURL + url, {
|
||||||
|
method,
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
credentials: "same-origin",
|
||||||
|
headers: {
|
||||||
|
"content-type": "application/json",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.then((e) => {
|
||||||
|
if (e.status !== 200) throw new Error(e.statusText);
|
||||||
|
return e.json();
|
||||||
|
})
|
||||||
|
.then((data) => {
|
||||||
|
if (data.error) {
|
||||||
|
if (redirect && data.additional && data.additional.auth) {
|
||||||
|
let state = btoa(
|
||||||
|
window.location.pathname + window.location.hash
|
||||||
|
);
|
||||||
|
window.location.href = `/login?state=${state}&base64=true`;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(data.error));
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
});
|
||||||
|
}
|
484
Frontend/src/helper/sha512.js
Normal file
484
Frontend/src/helper/sha512.js
Normal file
@ -0,0 +1,484 @@
|
|||||||
|
var b;
|
||||||
|
if (!(b = t)) {
|
||||||
|
var w = Math,
|
||||||
|
y = {},
|
||||||
|
B = (y.p = {}),
|
||||||
|
aa = function () {},
|
||||||
|
C = (B.A = {
|
||||||
|
extend: function (o) {
|
||||||
|
aa.prototype = this;
|
||||||
|
var _ = new aa();
|
||||||
|
return o && _.u(o), (_.z = this), _;
|
||||||
|
},
|
||||||
|
create: function () {
|
||||||
|
var o = this.extend();
|
||||||
|
return o.h.apply(o, arguments), o;
|
||||||
|
},
|
||||||
|
h: function () {},
|
||||||
|
u: function (o) {
|
||||||
|
for (var _ in o) o.hasOwnProperty(_) && (this[_] = o[_]);
|
||||||
|
o.hasOwnProperty("toString") && (this.toString = o.toString);
|
||||||
|
},
|
||||||
|
e: function () {
|
||||||
|
return this.z.extend(this);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
D = (B.i = C.extend({
|
||||||
|
h: function (o, _) {
|
||||||
|
(o = this.d = o || []), (this.c = void 0 == _ ? 4 * o.length : _);
|
||||||
|
},
|
||||||
|
toString: function (o) {
|
||||||
|
return (o || ba).stringify(this);
|
||||||
|
},
|
||||||
|
concat: function (o) {
|
||||||
|
var _ = this.d,
|
||||||
|
Da = o.d,
|
||||||
|
Ea = this.c,
|
||||||
|
o = o.c;
|
||||||
|
if ((this.t(), Ea % 4))
|
||||||
|
for (var Fa = 0; Fa < o; Fa++)
|
||||||
|
_[(Ea + Fa) >>> 2] |=
|
||||||
|
(255 & (Da[Fa >>> 2] >>> (24 - 8 * (Fa % 4)))) <<
|
||||||
|
(24 - 8 * ((Ea + Fa) % 4));
|
||||||
|
else if (65535 < Da.length)
|
||||||
|
for (Fa = 0; Fa < o; Fa += 4) _[(Ea + Fa) >>> 2] = Da[Fa >>> 2];
|
||||||
|
else _.push.apply(_, Da);
|
||||||
|
return (this.c += o), this;
|
||||||
|
},
|
||||||
|
t: function () {
|
||||||
|
var o = this.d,
|
||||||
|
_ = this.c;
|
||||||
|
(o[_ >>> 2] &= 4294967295 << (32 - 8 * (_ % 4))),
|
||||||
|
(o.length = w.ceil(_ / 4));
|
||||||
|
},
|
||||||
|
e: function () {
|
||||||
|
var o = C.e.call(this);
|
||||||
|
return (o.d = this.d.slice(0)), o;
|
||||||
|
},
|
||||||
|
random: function (o) {
|
||||||
|
for (var _ = [], Da = 0; Da < o; Da += 4)
|
||||||
|
_.push(0 | (4294967296 * w.random()));
|
||||||
|
return D.create(_, o);
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
ca = (y.O = {}),
|
||||||
|
ba = (ca.K = {
|
||||||
|
stringify: function (o) {
|
||||||
|
for (var Fa, _ = o.d, o = o.c, Da = [], Ea = 0; Ea < o; Ea++)
|
||||||
|
(Fa = 255 & (_[Ea >>> 2] >>> (24 - 8 * (Ea % 4)))),
|
||||||
|
Da.push((Fa >>> 4).toString(16)),
|
||||||
|
Da.push((15 & Fa).toString(16));
|
||||||
|
return Da.join("");
|
||||||
|
},
|
||||||
|
parse: function (o) {
|
||||||
|
for (var _ = o.length, Da = [], Ea = 0; Ea < _; Ea += 2)
|
||||||
|
Da[Ea >>> 3] |=
|
||||||
|
parseInt(o.substr(Ea, 2), 16) << (24 - 4 * (Ea % 8));
|
||||||
|
return D.create(Da, _ / 2);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
da = (ca.M = {
|
||||||
|
stringify: function (o) {
|
||||||
|
for (var _ = o.d, o = o.c, Da = [], Ea = 0; Ea < o; Ea++)
|
||||||
|
Da.push(
|
||||||
|
String.fromCharCode(
|
||||||
|
255 & (_[Ea >>> 2] >>> (24 - 8 * (Ea % 4)))
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return Da.join("");
|
||||||
|
},
|
||||||
|
parse: function (o) {
|
||||||
|
for (var _ = o.length, Da = [], Ea = 0; Ea < _; Ea++)
|
||||||
|
Da[Ea >>> 2] |= (255 & o.charCodeAt(Ea)) << (24 - 8 * (Ea % 4));
|
||||||
|
return D.create(Da, _);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ea = (ca.N = {
|
||||||
|
stringify: function (o) {
|
||||||
|
try {
|
||||||
|
return decodeURIComponent(escape(da.stringify(o)));
|
||||||
|
} catch (_) {
|
||||||
|
throw Error("Malformed UTF-8 data");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
parse: function (o) {
|
||||||
|
return da.parse(unescape(encodeURIComponent(o)));
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
ia = (B.I = C.extend({
|
||||||
|
reset: function () {
|
||||||
|
(this.g = D.create()), (this.j = 0);
|
||||||
|
},
|
||||||
|
l: function (o) {
|
||||||
|
"string" == typeof o && (o = ea.parse(o)),
|
||||||
|
this.g.concat(o),
|
||||||
|
(this.j += o.c);
|
||||||
|
},
|
||||||
|
m: function (o) {
|
||||||
|
var _ = this.g,
|
||||||
|
Da = _.d,
|
||||||
|
Ea = _.c,
|
||||||
|
Fa = this.n,
|
||||||
|
Ga = Ea / (4 * Fa),
|
||||||
|
Ga = o ? w.ceil(Ga) : w.max((0 | Ga) - this.r, 0),
|
||||||
|
o = Ga * Fa,
|
||||||
|
Ea = w.min(4 * o, Ea);
|
||||||
|
if (o) {
|
||||||
|
for (var Ha = 0; Ha < o; Ha += Fa) this.H(Da, Ha);
|
||||||
|
(Ha = Da.splice(0, o)), (_.c -= Ea);
|
||||||
|
}
|
||||||
|
return D.create(Ha, Ea);
|
||||||
|
},
|
||||||
|
e: function () {
|
||||||
|
var o = C.e.call(this);
|
||||||
|
return (o.g = this.g.e()), o;
|
||||||
|
},
|
||||||
|
r: 0,
|
||||||
|
}));
|
||||||
|
B.B = ia.extend({
|
||||||
|
h: function () {
|
||||||
|
this.reset();
|
||||||
|
},
|
||||||
|
reset: function () {
|
||||||
|
ia.reset.call(this), this.q();
|
||||||
|
},
|
||||||
|
update: function (o) {
|
||||||
|
return this.l(o), this.m(), this;
|
||||||
|
},
|
||||||
|
o: function (o) {
|
||||||
|
return o && this.l(o), this.G(), this.f;
|
||||||
|
},
|
||||||
|
e: function () {
|
||||||
|
var o = ia.e.call(this);
|
||||||
|
return (o.f = this.f.e()), o;
|
||||||
|
},
|
||||||
|
n: 16,
|
||||||
|
D: function (o) {
|
||||||
|
return function (_, Da) {
|
||||||
|
return o.create(Da).o(_);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
F: function (o) {
|
||||||
|
return function (_, Da) {
|
||||||
|
return ja.J.create(o, Da).o(_);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
var ja = (y.s = {});
|
||||||
|
b = y;
|
||||||
|
}
|
||||||
|
var t = b,
|
||||||
|
K = t,
|
||||||
|
ka = K.p,
|
||||||
|
la = ka.A,
|
||||||
|
va = ka.i,
|
||||||
|
K = (K.w = {});
|
||||||
|
(K.C = la.extend({
|
||||||
|
h: function (o, _) {
|
||||||
|
(this.a = o), (this.b = _);
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
(K.i = la.extend({
|
||||||
|
h: function (o, _) {
|
||||||
|
(o = this.d = o || []), (this.c = void 0 == _ ? 8 * o.length : _);
|
||||||
|
},
|
||||||
|
v: function () {
|
||||||
|
for (var Fa, o = this.d, _ = o.length, Da = [], Ea = 0; Ea < _; Ea++)
|
||||||
|
(Fa = o[Ea]), Da.push(Fa.a), Da.push(Fa.b);
|
||||||
|
return va.create(Da, this.c);
|
||||||
|
},
|
||||||
|
e: function () {
|
||||||
|
for (
|
||||||
|
var o = la.e.call(this),
|
||||||
|
_ = (o.d = this.d.slice(0)),
|
||||||
|
Da = _.length,
|
||||||
|
Ea = 0;
|
||||||
|
Ea < Da;
|
||||||
|
Ea++
|
||||||
|
)
|
||||||
|
_[Ea] = _[Ea].e();
|
||||||
|
return o;
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
function L() {
|
||||||
|
return wa.create.apply(wa, arguments);
|
||||||
|
}
|
||||||
|
for (
|
||||||
|
var xa = t.p.B,
|
||||||
|
M = t.w,
|
||||||
|
wa = M.C,
|
||||||
|
ya = M.i,
|
||||||
|
M = t.s,
|
||||||
|
za = [
|
||||||
|
L(1116352408, 3609767458),
|
||||||
|
L(1899447441, 602891725),
|
||||||
|
L(3049323471, 3964484399),
|
||||||
|
L(3921009573, 2173295548),
|
||||||
|
L(961987163, 4081628472),
|
||||||
|
L(1508970993, 3053834265),
|
||||||
|
L(2453635748, 2937671579),
|
||||||
|
L(2870763221, 3664609560),
|
||||||
|
L(3624381080, 2734883394),
|
||||||
|
L(310598401, 1164996542),
|
||||||
|
L(607225278, 1323610764),
|
||||||
|
L(1426881987, 3590304994),
|
||||||
|
L(1925078388, 4068182383),
|
||||||
|
L(2162078206, 991336113),
|
||||||
|
L(2614888103, 633803317),
|
||||||
|
L(3248222580, 3479774868),
|
||||||
|
L(3835390401, 2666613458),
|
||||||
|
L(4022224774, 944711139),
|
||||||
|
L(264347078, 2341262773),
|
||||||
|
L(604807628, 2007800933),
|
||||||
|
L(770255983, 1495990901),
|
||||||
|
L(1249150122, 1856431235),
|
||||||
|
L(1555081692, 3175218132),
|
||||||
|
L(1996064986, 2198950837),
|
||||||
|
L(2554220882, 3999719339),
|
||||||
|
L(2821834349, 766784016),
|
||||||
|
L(2952996808, 2566594879),
|
||||||
|
L(3210313671, 3203337956),
|
||||||
|
L(3336571891, 1034457026),
|
||||||
|
L(3584528711, 2466948901),
|
||||||
|
L(113926993, 3758326383),
|
||||||
|
L(338241895, 168717936),
|
||||||
|
L(666307205, 1188179964),
|
||||||
|
L(773529912, 1546045734),
|
||||||
|
L(1294757372, 1522805485),
|
||||||
|
L(1396182291, 2643833823),
|
||||||
|
L(1695183700, 2343527390),
|
||||||
|
L(1986661051, 1014477480),
|
||||||
|
L(2177026350, 1206759142),
|
||||||
|
L(2456956037, 344077627),
|
||||||
|
L(2730485921, 1290863460),
|
||||||
|
L(2820302411, 3158454273),
|
||||||
|
L(3259730800, 3505952657),
|
||||||
|
L(3345764771, 106217008),
|
||||||
|
L(3516065817, 3606008344),
|
||||||
|
L(3600352804, 1432725776),
|
||||||
|
L(4094571909, 1467031594),
|
||||||
|
L(275423344, 851169720),
|
||||||
|
L(430227734, 3100823752),
|
||||||
|
L(506948616, 1363258195),
|
||||||
|
L(659060556, 3750685593),
|
||||||
|
L(883997877, 3785050280),
|
||||||
|
L(958139571, 3318307427),
|
||||||
|
L(1322822218, 3812723403),
|
||||||
|
L(1537002063, 2003034995),
|
||||||
|
L(1747873779, 3602036899),
|
||||||
|
L(1955562222, 1575990012),
|
||||||
|
L(2024104815, 1125592928),
|
||||||
|
L(2227730452, 2716904306),
|
||||||
|
L(2361852424, 442776044),
|
||||||
|
L(2428436474, 593698344),
|
||||||
|
L(2756734187, 3733110249),
|
||||||
|
L(3204031479, 2999351573),
|
||||||
|
L(3329325298, 3815920427),
|
||||||
|
L(3391569614, 3928383900),
|
||||||
|
L(3515267271, 566280711),
|
||||||
|
L(3940187606, 3454069534),
|
||||||
|
L(4118630271, 4000239992),
|
||||||
|
L(116418474, 1914138554),
|
||||||
|
L(174292421, 2731055270),
|
||||||
|
L(289380356, 3203993006),
|
||||||
|
L(460393269, 320620315),
|
||||||
|
L(685471733, 587496836),
|
||||||
|
L(852142971, 1086792851),
|
||||||
|
L(1017036298, 365543100),
|
||||||
|
L(1126000580, 2618297676),
|
||||||
|
L(1288033470, 3409855158),
|
||||||
|
L(1501505948, 4234509866),
|
||||||
|
L(1607167915, 987167468),
|
||||||
|
L(1816402316, 1246189591),
|
||||||
|
],
|
||||||
|
$ = [],
|
||||||
|
Aa = 0;
|
||||||
|
80 > Aa;
|
||||||
|
Aa++
|
||||||
|
)
|
||||||
|
$[Aa] = L();
|
||||||
|
(M = M.k = xa.extend({
|
||||||
|
q: function () {
|
||||||
|
this.f = ya.create([
|
||||||
|
L(1779033703, 4089235720),
|
||||||
|
L(3144134277, 2227873595),
|
||||||
|
L(1013904242, 4271175723),
|
||||||
|
L(2773480762, 1595750129),
|
||||||
|
L(1359893119, 2917565137),
|
||||||
|
L(2600822924, 725511199),
|
||||||
|
L(528734635, 4215389547),
|
||||||
|
L(1541459225, 327033209),
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
H: function (o, _) {
|
||||||
|
for (
|
||||||
|
var qb,
|
||||||
|
Da = this.f.d,
|
||||||
|
Ea = Da[0],
|
||||||
|
Fa = Da[1],
|
||||||
|
Ga = Da[2],
|
||||||
|
Ha = Da[3],
|
||||||
|
Ia = Da[4],
|
||||||
|
Ja = Da[5],
|
||||||
|
Ka = Da[6],
|
||||||
|
Da = Da[7],
|
||||||
|
La = Ea.a,
|
||||||
|
Ma = Ea.b,
|
||||||
|
Na = Fa.a,
|
||||||
|
Oa = Fa.b,
|
||||||
|
Pa = Ga.a,
|
||||||
|
Qa = Ga.b,
|
||||||
|
Ra = Ha.a,
|
||||||
|
Sa = Ha.b,
|
||||||
|
Ta = Ia.a,
|
||||||
|
Ua = Ia.b,
|
||||||
|
Va = Ja.a,
|
||||||
|
Wa = Ja.b,
|
||||||
|
Xa = Ka.a,
|
||||||
|
Ya = Ka.b,
|
||||||
|
Za = Da.a,
|
||||||
|
$a = Da.b,
|
||||||
|
_a = La,
|
||||||
|
ab = Ma,
|
||||||
|
bb = Na,
|
||||||
|
cb = Oa,
|
||||||
|
db = Pa,
|
||||||
|
eb = Qa,
|
||||||
|
fb = Ra,
|
||||||
|
gb = Sa,
|
||||||
|
hb = Ta,
|
||||||
|
ib = Ua,
|
||||||
|
jb = Va,
|
||||||
|
kb = Wa,
|
||||||
|
lb = Xa,
|
||||||
|
mb = Ya,
|
||||||
|
nb = Za,
|
||||||
|
ob = $a,
|
||||||
|
pb = 0;
|
||||||
|
80 > pb;
|
||||||
|
pb++
|
||||||
|
) {
|
||||||
|
if (((qb = $[pb]), 16 > pb))
|
||||||
|
var rb = (qb.a = 0 | o[_ + 2 * pb]),
|
||||||
|
sb = (qb.b = 0 | o[_ + 2 * pb + 1]);
|
||||||
|
else {
|
||||||
|
var rb = $[pb - 15],
|
||||||
|
sb = rb.a,
|
||||||
|
tb = rb.b,
|
||||||
|
rb =
|
||||||
|
((tb << 31) | (sb >>> 1)) ^
|
||||||
|
((tb << 24) | (sb >>> 8)) ^
|
||||||
|
(sb >>> 7),
|
||||||
|
tb =
|
||||||
|
((sb << 31) | (tb >>> 1)) ^
|
||||||
|
((sb << 24) | (tb >>> 8)) ^
|
||||||
|
((sb << 25) | (tb >>> 7)),
|
||||||
|
ub = $[pb - 2],
|
||||||
|
sb = ub.a,
|
||||||
|
vb = ub.b,
|
||||||
|
ub =
|
||||||
|
((vb << 13) | (sb >>> 19)) ^
|
||||||
|
((sb << 3) | (vb >>> 29)) ^
|
||||||
|
(sb >>> 6),
|
||||||
|
vb =
|
||||||
|
((sb << 13) | (vb >>> 19)) ^
|
||||||
|
((vb << 3) | (sb >>> 29)) ^
|
||||||
|
((sb << 26) | (vb >>> 6)),
|
||||||
|
sb = $[pb - 7],
|
||||||
|
wb = sb.a,
|
||||||
|
xb = $[pb - 16],
|
||||||
|
yb = xb.a,
|
||||||
|
xb = xb.b,
|
||||||
|
sb = tb + sb.b,
|
||||||
|
rb = rb + wb + (sb >>> 0 < tb >>> 0 ? 1 : 0),
|
||||||
|
sb = sb + vb,
|
||||||
|
rb = rb + ub + (sb >>> 0 < vb >>> 0 ? 1 : 0),
|
||||||
|
sb = sb + xb,
|
||||||
|
rb = rb + yb + (sb >>> 0 < xb >>> 0 ? 1 : 0);
|
||||||
|
(qb.a = rb), (qb.b = sb);
|
||||||
|
}
|
||||||
|
var wb = (hb & jb) ^ (~hb & lb),
|
||||||
|
xb = (ib & kb) ^ (~ib & mb),
|
||||||
|
qb = (_a & bb) ^ (_a & db) ^ (bb & db),
|
||||||
|
tb =
|
||||||
|
((ab << 4) | (_a >>> 28)) ^
|
||||||
|
((_a << 30) | (ab >>> 2)) ^
|
||||||
|
((_a << 25) | (ab >>> 7)),
|
||||||
|
ub =
|
||||||
|
((_a << 4) | (ab >>> 28)) ^
|
||||||
|
((ab << 30) | (_a >>> 2)) ^
|
||||||
|
((ab << 25) | (_a >>> 7)),
|
||||||
|
vb = za[pb],
|
||||||
|
Ab = vb.a,
|
||||||
|
Bb = vb.b,
|
||||||
|
vb =
|
||||||
|
ob +
|
||||||
|
(((hb << 18) | (ib >>> 14)) ^
|
||||||
|
((hb << 14) | (ib >>> 18)) ^
|
||||||
|
((ib << 23) | (hb >>> 9))),
|
||||||
|
yb =
|
||||||
|
nb +
|
||||||
|
(((ib << 18) | (hb >>> 14)) ^
|
||||||
|
((ib << 14) | (hb >>> 18)) ^
|
||||||
|
((hb << 23) | (ib >>> 9))) +
|
||||||
|
(vb >>> 0 < ob >>> 0 ? 1 : 0),
|
||||||
|
vb = vb + xb,
|
||||||
|
yb = yb + wb + (vb >>> 0 < xb >>> 0 ? 1 : 0),
|
||||||
|
vb = vb + Bb,
|
||||||
|
yb = yb + Ab + (vb >>> 0 < Bb >>> 0 ? 1 : 0),
|
||||||
|
vb = vb + sb,
|
||||||
|
yb = yb + rb + (vb >>> 0 < sb >>> 0 ? 1 : 0),
|
||||||
|
sb = ub + ((ab & cb) ^ (ab & eb) ^ (cb & eb)),
|
||||||
|
qb = tb + qb + (sb >>> 0 < ub >>> 0 ? 1 : 0),
|
||||||
|
nb = lb,
|
||||||
|
ob = mb,
|
||||||
|
lb = jb,
|
||||||
|
mb = kb,
|
||||||
|
jb = hb,
|
||||||
|
kb = ib,
|
||||||
|
ib = 0 | (gb + vb),
|
||||||
|
hb = 0 | (fb + yb + (ib >>> 0 < gb >>> 0 ? 1 : 0)),
|
||||||
|
fb = db,
|
||||||
|
gb = eb,
|
||||||
|
db = bb,
|
||||||
|
eb = cb,
|
||||||
|
bb = _a,
|
||||||
|
cb = ab,
|
||||||
|
ab = 0 | (vb + sb),
|
||||||
|
_a = 0 | (yb + qb + (ab >>> 0 < vb >>> 0 ? 1 : 0));
|
||||||
|
}
|
||||||
|
(Ma = Ea.b = 0 | (Ma + ab)),
|
||||||
|
(Ea.a = 0 | (La + _a + (Ma >>> 0 < ab >>> 0 ? 1 : 0))),
|
||||||
|
(Oa = Fa.b = 0 | (Oa + cb)),
|
||||||
|
(Fa.a = 0 | (Na + bb + (Oa >>> 0 < cb >>> 0 ? 1 : 0))),
|
||||||
|
(Qa = Ga.b = 0 | (Qa + eb)),
|
||||||
|
(Ga.a = 0 | (Pa + db + (Qa >>> 0 < eb >>> 0 ? 1 : 0))),
|
||||||
|
(Sa = Ha.b = 0 | (Sa + gb)),
|
||||||
|
(Ha.a = 0 | (Ra + fb + (Sa >>> 0 < gb >>> 0 ? 1 : 0))),
|
||||||
|
(Ua = Ia.b = 0 | (Ua + ib)),
|
||||||
|
(Ia.a = 0 | (Ta + hb + (Ua >>> 0 < ib >>> 0 ? 1 : 0))),
|
||||||
|
(Wa = Ja.b = 0 | (Wa + kb)),
|
||||||
|
(Ja.a = 0 | (Va + jb + (Wa >>> 0 < kb >>> 0 ? 1 : 0))),
|
||||||
|
(Ya = Ka.b = 0 | (Ya + mb)),
|
||||||
|
(Ka.a = 0 | (Xa + lb + (Ya >>> 0 < mb >>> 0 ? 1 : 0))),
|
||||||
|
($a = Da.b = 0 | ($a + ob)),
|
||||||
|
(Da.a = 0 | (Za + nb + ($a >>> 0 < ob >>> 0 ? 1 : 0)));
|
||||||
|
},
|
||||||
|
G: function () {
|
||||||
|
var o = this.g,
|
||||||
|
_ = o.d,
|
||||||
|
Da = 8 * this.j,
|
||||||
|
Ea = 8 * o.c;
|
||||||
|
(_[Ea >>> 5] |= 128 << (24 - (Ea % 32))),
|
||||||
|
(_[(((Ea + 128) >>> 10) << 5) + 31] = Da),
|
||||||
|
(o.c = 4 * _.length),
|
||||||
|
this.m(),
|
||||||
|
(this.f = this.f.v());
|
||||||
|
},
|
||||||
|
n: 32,
|
||||||
|
})),
|
||||||
|
(t.k = xa.D(M)),
|
||||||
|
(t.L = xa.F(M));
|
||||||
|
export default function sha512(o) {
|
||||||
|
return t.k(o) + "";
|
||||||
|
}
|
44
Frontend/src/pages/home/App.svelte
Normal file
44
Frontend/src/pages/home/App.svelte
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
<style>
|
||||||
|
.main {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
list-style: none;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
li > a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="main">
|
||||||
|
<h1>Home Page</h1>
|
||||||
|
|
||||||
|
<h2>About</h2>
|
||||||
|
<p>
|
||||||
|
OpenAuth is a Service to provide simple Authentication to a veriaty of
|
||||||
|
Applications. With a simple to use API and different Strategies, it can be
|
||||||
|
easily integrated into most Applications.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>QickLinks</h2>
|
||||||
|
<p>
|
||||||
|
If you want to manage your Account, click
|
||||||
|
<a href="user.html">here</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>Applications using OpenAuth</h2>
|
||||||
|
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<a href="https://ebook.stamm.me">EBook Store and Reader</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="https://notes.hibas123.de">
|
||||||
|
Secure and Simple Notes application
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
8
Frontend/src/pages/home/main.ts
Normal file
8
Frontend/src/pages/home/main.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import "../../components/theme";
|
||||||
|
import App from "./App.svelte";
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body,
|
||||||
|
});
|
||||||
|
|
||||||
|
export default app;
|
124
Frontend/src/pages/login/App.svelte
Normal file
124
Frontend/src/pages/login/App.svelte
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
<script>
|
||||||
|
import Theme from "../../components/theme";
|
||||||
|
import HoveringContentBox from "../../components/HoveringContentBox.svelte";
|
||||||
|
import Api from "./api.ts";
|
||||||
|
import Credentials from "./Credentials.svelte";
|
||||||
|
import Redirect from "./Redirect.svelte";
|
||||||
|
import Twofactor from "./Twofactor.svelte";
|
||||||
|
|
||||||
|
const appname = "OpenAuth";
|
||||||
|
|
||||||
|
const states = {
|
||||||
|
credentials: 1,
|
||||||
|
twofactor: 3,
|
||||||
|
redirect: 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
let username = Api.getUsername();
|
||||||
|
let password = "";
|
||||||
|
|
||||||
|
let loading = false;
|
||||||
|
let state = states.credentials;
|
||||||
|
|
||||||
|
function getButtonText(state) {
|
||||||
|
switch (state) {
|
||||||
|
case states.username:
|
||||||
|
return "Next";
|
||||||
|
case states.password:
|
||||||
|
return "Login";
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$: btnText = getButtonText(state);
|
||||||
|
|
||||||
|
let error;
|
||||||
|
|
||||||
|
// window.addEventListener("popstate", () => {
|
||||||
|
// state = history.state;
|
||||||
|
// })
|
||||||
|
|
||||||
|
function LoadRedirect() {
|
||||||
|
state = states.redirect;
|
||||||
|
}
|
||||||
|
|
||||||
|
function Loading() {
|
||||||
|
state = states.loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
let salt;
|
||||||
|
async function buttonClick() {
|
||||||
|
if (state === states.username) {
|
||||||
|
Loading();
|
||||||
|
let res = await Api.setUsername(username);
|
||||||
|
if (res.error) {
|
||||||
|
error = res.error;
|
||||||
|
LoadUsername();
|
||||||
|
} else {
|
||||||
|
LoadPassword();
|
||||||
|
}
|
||||||
|
} else if (state === states.password) {
|
||||||
|
Loading();
|
||||||
|
let res = await Api.setPassword(password);
|
||||||
|
if (res.error) {
|
||||||
|
error = res.error;
|
||||||
|
LoadPassword();
|
||||||
|
} else {
|
||||||
|
if (res.tfa) {
|
||||||
|
// TODO: Make TwoFactor UI/-s
|
||||||
|
} else {
|
||||||
|
LoadRedirect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnText = "Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function startRedirect() {
|
||||||
|
state = states.redirect;
|
||||||
|
// Show message to User and then redirect
|
||||||
|
setTimeout(() => Api.finish(), 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterCredentials() {
|
||||||
|
Object.keys(Api); // Some weird bug needs this???
|
||||||
|
|
||||||
|
if (Api.twofactor) {
|
||||||
|
state = states.twofactor;
|
||||||
|
} else {
|
||||||
|
startRedirect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterTwoFactor() {
|
||||||
|
startRedirect();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
footer {
|
||||||
|
text-align: center;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<Theme>
|
||||||
|
<HoveringContentBox title="Login" {loading}>
|
||||||
|
<form action="JavaScript:void(0)">
|
||||||
|
{#if state === states.redirect}
|
||||||
|
<Redirect />
|
||||||
|
{:else if state === states.credentials}
|
||||||
|
<Credentials next={afterCredentials} setLoading={(s) => (loading = s)} />
|
||||||
|
{:else if state === states.twofactor}
|
||||||
|
<Twofactor finish={afterTwoFactor} setLoading={(s) => (loading = s)} />
|
||||||
|
{/if}
|
||||||
|
</form>
|
||||||
|
</HoveringContentBox>
|
||||||
|
<footer>
|
||||||
|
<p>Powered by {appname}</p>
|
||||||
|
</footer>
|
||||||
|
</Theme>
|
84
Frontend/src/pages/login/Credentials.svelte
Normal file
84
Frontend/src/pages/login/Credentials.svelte
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<script>
|
||||||
|
import Api from "./api.ts";
|
||||||
|
|
||||||
|
let error;
|
||||||
|
let password = "";
|
||||||
|
let username = Api.getUsername();
|
||||||
|
|
||||||
|
const states = {
|
||||||
|
username: 1,
|
||||||
|
password: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = states.username;
|
||||||
|
|
||||||
|
let salt;
|
||||||
|
|
||||||
|
export let setLoading;
|
||||||
|
export let next;
|
||||||
|
|
||||||
|
async function buttonClick() {
|
||||||
|
setLoading(true);
|
||||||
|
if (state === states.username) {
|
||||||
|
let res = await Api.setUsername(username);
|
||||||
|
if (res.error) {
|
||||||
|
error = res.error;
|
||||||
|
} else {
|
||||||
|
state = states.password;
|
||||||
|
error = undefined;
|
||||||
|
}
|
||||||
|
} else if (state === states.password) {
|
||||||
|
let res = await Api.setPassword(password);
|
||||||
|
if (res.error) {
|
||||||
|
error = res.error;
|
||||||
|
} else {
|
||||||
|
error = undefined;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
padding: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wide-button {
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
{#if state === states.username}
|
||||||
|
<h3>Enter your Username or your E-Mail Address</h3>
|
||||||
|
<div class="floating group">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
autocomplete="username"
|
||||||
|
autofocus
|
||||||
|
bind:value={username} />
|
||||||
|
<span class="highlight" />
|
||||||
|
<span class="bar" />
|
||||||
|
<label>Username or E-Mail</label>
|
||||||
|
<div class="error" style={!error ? 'display: none;' : ''}>{error}</div>
|
||||||
|
</div>
|
||||||
|
{:else}
|
||||||
|
<h3>Enter password for {username}</h3>
|
||||||
|
<div class="floating group">
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
autocomplete="password"
|
||||||
|
autofocus
|
||||||
|
bind:value={password} />
|
||||||
|
<span class="highlight" />
|
||||||
|
<span class="bar" />
|
||||||
|
<label>Password</label>
|
||||||
|
<div class="error" style={!error ? 'display: none;' : ''}>{error}</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<button class="btn btn-primary wide-button" on:click={buttonClick}>Next</button>
|
99
Frontend/src/pages/login/Redirect.svelte
Normal file
99
Frontend/src/pages/login/Redirect.svelte
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
<script>
|
||||||
|
// import {
|
||||||
|
// onMount,
|
||||||
|
// onDestroy
|
||||||
|
// } from "svelte";
|
||||||
|
import { onMount, onDestroy } from "svelte";
|
||||||
|
|
||||||
|
const basetext = "Logged in. Redirecting";
|
||||||
|
let dots = 0;
|
||||||
|
|
||||||
|
$: text = basetext + ".".repeat(dots);
|
||||||
|
|
||||||
|
let iv;
|
||||||
|
onMount(() => {
|
||||||
|
console.log("Mounted");
|
||||||
|
iv = setInterval(() => {
|
||||||
|
dots++;
|
||||||
|
if (dots > 3) dots = 0;
|
||||||
|
}, 500);
|
||||||
|
});
|
||||||
|
|
||||||
|
onDestroy(() => {
|
||||||
|
console.log("on Destroy");
|
||||||
|
clearInterval(iv);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.checkmark__circle {
|
||||||
|
stroke-dasharray: 166;
|
||||||
|
stroke-dashoffset: 166;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
stroke: #7ac142;
|
||||||
|
fill: none;
|
||||||
|
animation: stroke 0.6s cubic-bezier(0.65, 0, 0.45, 1) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: block;
|
||||||
|
stroke-width: 2;
|
||||||
|
stroke: #fff;
|
||||||
|
stroke-miterlimit: 10;
|
||||||
|
margin: 10% auto;
|
||||||
|
box-shadow: inset 0px 0px 0px #7ac142;
|
||||||
|
animation: fill 0.4s ease-in-out 0.4s forwards,
|
||||||
|
scale 0.3s ease-in-out 0.9s both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkmark__check {
|
||||||
|
transform-origin: 50% 50%;
|
||||||
|
stroke-dasharray: 48;
|
||||||
|
stroke-dashoffset: 48;
|
||||||
|
animation: stroke 0.3s cubic-bezier(0.65, 0, 0.45, 1) 0.8s forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes stroke {
|
||||||
|
100% {
|
||||||
|
stroke-dashoffset: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
transform: scale3d(1.1, 1.1, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fill {
|
||||||
|
100% {
|
||||||
|
box-shadow: inset 0px 0px 0px 30px #7ac142;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scale {
|
||||||
|
transform: scale(1.5);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="scale">
|
||||||
|
<svg class="checkmark" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 52 52">
|
||||||
|
<circle class="checkmark__circle" cx="26" cy="26" r="25" fill="none" />
|
||||||
|
<path
|
||||||
|
class="checkmark__check"
|
||||||
|
fill="none"
|
||||||
|
d="M14.1 27.2l7.1 7.2 16.7-16.8" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<!-- <div style="text-align: center;"> -->
|
||||||
|
<h3>{text}</h3>
|
||||||
|
<!-- </div> -->
|
104
Frontend/src/pages/login/Twofactor.svelte
Normal file
104
Frontend/src/pages/login/Twofactor.svelte
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
<script>
|
||||||
|
import Api, { TFATypes } from "./api.ts";
|
||||||
|
import Icon from "./icons/Icon.svelte";
|
||||||
|
|
||||||
|
import OTCTwoFactor from "./twofactors/otc.svelte";
|
||||||
|
import PushTwoFactor from "./twofactors/push.svelte";
|
||||||
|
import U2FTwoFactor from "./twofactors/u2f.svelte";
|
||||||
|
|
||||||
|
const states = {
|
||||||
|
list: 1,
|
||||||
|
twofactor: 2
|
||||||
|
};
|
||||||
|
|
||||||
|
function getIcon(tf) {
|
||||||
|
switch (tf.type) {
|
||||||
|
case TFATypes.OTC:
|
||||||
|
return "Authenticator";
|
||||||
|
case TFATypes.BACKUP_CODE:
|
||||||
|
return "BackupCode";
|
||||||
|
case TFATypes.U2F:
|
||||||
|
return "SecurityKey";
|
||||||
|
case TFATypes.APP_ALLOW:
|
||||||
|
return "AppPush";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let twofactors = Api.twofactor.map(tf => {
|
||||||
|
return {
|
||||||
|
...tf,
|
||||||
|
icon: getIcon(tf)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
let state = states.list;
|
||||||
|
|
||||||
|
let twofactor = undefined;
|
||||||
|
twofactor = twofactors[0];
|
||||||
|
$: console.log(twofactor);
|
||||||
|
|
||||||
|
function onFinish(res) {
|
||||||
|
if (res) finish();
|
||||||
|
else twofactor = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export let finish;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-inline-start: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
border-top: 1px grey solid;
|
||||||
|
padding: 1em;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
li:first-child {
|
||||||
|
border-top: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
float: left;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
margin-left: 48px;
|
||||||
|
line-height: 24px;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
{#if !twofactor}
|
||||||
|
<h3>Select your Authentication method:</h3>
|
||||||
|
<ul>
|
||||||
|
{#each twofactors as tf}
|
||||||
|
<li on:click={() => (twofactor = tf)}>
|
||||||
|
<div class="icon">
|
||||||
|
<Icon icon_name={tf.icon} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="name">{tf.name}</div>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
{:else if twofactor.type === TFATypes.OTC}
|
||||||
|
<OTCTwoFactor id={twofactor.id} finish={onFinish} otc={true} />
|
||||||
|
{:else if twofactor.type === TFATypes.BACKUP_CODE}
|
||||||
|
<OTCTwoFactor id={twofactor.id} finish={onFinish} otc={false} />
|
||||||
|
{:else if twofactor.type === TFATypes.U2F}
|
||||||
|
<U2FTwoFactor id={twofactor.id} finish={onFinish} />
|
||||||
|
{:else if twofactor.type === TFATypes.APP_ALLOW}
|
||||||
|
<PushTwoFactor id={twofactor.id} finish={onFinish} />
|
||||||
|
{:else}
|
||||||
|
<div>Invalid TwoFactor Method!</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
</div>
|
182
Frontend/src/pages/login/api.ts
Normal file
182
Frontend/src/pages/login/api.ts
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
import request from "../../helper/request";
|
||||||
|
import sha from "../../helper/sha512";
|
||||||
|
import { setCookie, getCookie } from "../../helper/cookie";
|
||||||
|
|
||||||
|
export interface TwoFactor {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: TFATypes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum TFATypes {
|
||||||
|
OTC,
|
||||||
|
BACKUP_CODE,
|
||||||
|
U2F,
|
||||||
|
APP_ALLOW,
|
||||||
|
}
|
||||||
|
|
||||||
|
// const Api = {
|
||||||
|
// // twofactor: [{
|
||||||
|
// // id: "1",
|
||||||
|
// // name: "Backup Codes",
|
||||||
|
// // type: TFATypes.BACKUP_CODE
|
||||||
|
// // }, {
|
||||||
|
// // id: "2",
|
||||||
|
// // name: "YubiKey",
|
||||||
|
// // type: TFATypes.U2F
|
||||||
|
// // }, {
|
||||||
|
// // id: "3",
|
||||||
|
// // name: "Authenticator",
|
||||||
|
// // type: TFATypes.OTC
|
||||||
|
// // }] as TwoFactor[],
|
||||||
|
|
||||||
|
// }
|
||||||
|
|
||||||
|
export interface IToken {
|
||||||
|
token: string;
|
||||||
|
expires: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function makeid(length) {
|
||||||
|
var result = "";
|
||||||
|
var characters =
|
||||||
|
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||||
|
var charactersLength = characters.length;
|
||||||
|
for (var i = 0; i < length; i++) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * charactersLength));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class Api {
|
||||||
|
static salt: string;
|
||||||
|
static login: IToken;
|
||||||
|
static special: IToken;
|
||||||
|
static username: string;
|
||||||
|
|
||||||
|
static twofactor: any[];
|
||||||
|
|
||||||
|
static getUsername() {
|
||||||
|
return this.username || getCookie("username");
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setUsername(
|
||||||
|
username: string
|
||||||
|
): Promise<{ error: string | undefined }> {
|
||||||
|
return request(
|
||||||
|
"/api/user/login",
|
||||||
|
{
|
||||||
|
type: "username",
|
||||||
|
username,
|
||||||
|
},
|
||||||
|
"POST"
|
||||||
|
)
|
||||||
|
.then((res) => {
|
||||||
|
this.salt = res.salt;
|
||||||
|
this.username = username;
|
||||||
|
return {
|
||||||
|
error: undefined,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
let error = err.message;
|
||||||
|
return { error };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static async setPassword(
|
||||||
|
password: string
|
||||||
|
): Promise<{ error: string | undefined; twofactor?: any }> {
|
||||||
|
const date = new Date().valueOf();
|
||||||
|
let pw = sha(sha(this.salt + password) + date.toString());
|
||||||
|
return request(
|
||||||
|
"/api/user/login",
|
||||||
|
{
|
||||||
|
type: "password",
|
||||||
|
},
|
||||||
|
"POST",
|
||||||
|
{
|
||||||
|
username: this.username,
|
||||||
|
password: pw,
|
||||||
|
date,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.then(({ login, special, tfa }) => {
|
||||||
|
this.login = login;
|
||||||
|
this.special = special;
|
||||||
|
|
||||||
|
if (tfa && Array.isArray(tfa) && tfa.length > 0)
|
||||||
|
this.twofactor = tfa;
|
||||||
|
else this.twofactor = undefined;
|
||||||
|
|
||||||
|
return {
|
||||||
|
error: undefined,
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
let error = err.message;
|
||||||
|
return { error };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static gettok() {
|
||||||
|
return {
|
||||||
|
login: this.login.token,
|
||||||
|
special: this.special.token,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
static async sendBackup(id: string, code: string) {
|
||||||
|
return request("/api/user/twofactor/backup", this.gettok(), "PUT", {
|
||||||
|
code,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
.then(({ login_exp, special_exp }) => {
|
||||||
|
this.login.expires = login_exp;
|
||||||
|
this.special.expires = special_exp;
|
||||||
|
return {};
|
||||||
|
})
|
||||||
|
.catch((err) => ({ error: err.message }));
|
||||||
|
}
|
||||||
|
|
||||||
|
static async sendOTC(id: string, code: string) {
|
||||||
|
return request("/api/user/twofactor/otc", this.gettok(), "PUT", {
|
||||||
|
code,
|
||||||
|
id,
|
||||||
|
})
|
||||||
|
.then(({ login_exp, special_exp }) => {
|
||||||
|
this.login.expires = login_exp;
|
||||||
|
this.special.expires = special_exp;
|
||||||
|
return {};
|
||||||
|
})
|
||||||
|
.catch((error) => ({ error: error.message }));
|
||||||
|
}
|
||||||
|
|
||||||
|
static finish() {
|
||||||
|
let d = new Date();
|
||||||
|
d.setTime(d.getTime() + 30 * 24 * 60 * 60 * 1000); //Keep the username 30 days
|
||||||
|
setCookie("username", this.username, d.toUTCString());
|
||||||
|
|
||||||
|
setCookie(
|
||||||
|
"login",
|
||||||
|
this.login.token,
|
||||||
|
new Date(this.login.expires).toUTCString()
|
||||||
|
);
|
||||||
|
setCookie(
|
||||||
|
"special",
|
||||||
|
this.special.token,
|
||||||
|
new Date(this.special.expires).toUTCString()
|
||||||
|
);
|
||||||
|
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
let state = url.searchParams.get("state");
|
||||||
|
let red = "/";
|
||||||
|
|
||||||
|
if (state) {
|
||||||
|
let base64 = url.searchParams.get("base64");
|
||||||
|
if (base64) red = atob(state);
|
||||||
|
else red = state;
|
||||||
|
}
|
||||||
|
setTimeout(() => (window.location.href = red), 200);
|
||||||
|
}
|
||||||
|
}
|
1
Frontend/src/pages/login/icons/AppPush.svg
Normal file
1
Frontend/src/pages/login/icons/AppPush.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18.617,1.72c0,-0.949 -0.771,-1.72 -1.721,-1.72l-9.792,0c-0.95,0 -1.721,0.771 -1.721,1.72l0,20.56c0,0.949 0.771,1.72 1.721,1.72l9.792,0c0.95,0 1.721,-0.771 1.721,-1.72l0,-20.56Z" style="fill:#4d4d4d;"/><rect x="6" y="3" width="12" height="18" style="fill:#b3b3b3;"/><path d="M14,1.5c0,-0.129 -0.105,-0.233 -0.233,-0.233l-3.534,0c-0.128,0 -0.233,0.104 -0.233,0.233c0,0.129 0.105,0.233 0.233,0.233l3.534,0c0.128,0 0.233,-0.104 0.233,-0.233Z" style="fill:#b3b3b3;"/><ellipse cx="12" cy="22.5" rx="0.983" ry="1" style="fill:#b3b3b3;"/></svg>
|
After Width: | Height: | Size: 992 B |
1
Frontend/src/pages/login/icons/Authenticator.svg
Normal file
1
Frontend/src/pages/login/icons/Authenticator.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><path d="M18.5,12c0,3.59 -2.91,6.5 -6.5,6.5c-3.59,0 -6.5,-2.91 -6.5,-6.5c0,-3.59 2.91,-6.5 6.5,-6.5c1.729,0 3.295,0.679 4.46,1.78l4.169,-3.599c-2.184,-2.265 -5.242,-3.681 -8.629,-3.681c-6.617,0 -12,5.383 -12,12c0,6.617 5.383,12 12,12c6.617,0 12,-5.383 12,-12l-5.5,0Z" style="fill:#999;fill-rule:nonzero;"/><circle id="XMLID_1331_" cx="12" cy="12" r="12" style="fill:#808080;"/><path d="M19,12c0,3.866 -3.134,7 -7,7c-3.866,0 -7,-3.134 -7,-7c0,-3.866 3.134,-7 7,-7c1.88,0 3.583,0.745 4.841,1.951l3.788,-3.27c-2.184,-2.265 -5.242,-3.681 -8.629,-3.681c-6.617,0 -12,5.383 -12,12c0,6.617 5.383,12 12,12c6.617,0 12,-5.383 12,-12l-5,0Z" style="fill:#999;fill-rule:nonzero;"/><circle cx="12" cy="2.5" r="1" style="fill:#b3b3b3;"/><circle cx="12" cy="21.5" r="1" style="fill:#b3b3b3;"/><circle cx="2.5" cy="12" r="1" style="fill:#b3b3b3;"/><path d="M4.575,18.01c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><path d="M18.01,18.01c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><path d="M4.575,4.575c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><circle id="XMLID_1329_" cx="12" cy="12" r="6" style="fill:#808080;"/><circle id="XMLID_1330_" cx="12" cy="12" r="7" style="fill:#808080;"/><path d="M19,12.25c0,-0.042 -0.006,-0.083 -0.006,-0.125c-0.068,3.808 -3.17,6.875 -6.994,6.875c-3.824,0 -6.933,-3.067 -7,-6.875c-0.001,0.042 0,0.083 0,0.125c0,3.866 3.134,7 7,7c3.866,0 7,-3.134 7,-7Z" style="fill:#fff;fill-opacity:0.2;fill-rule:nonzero;"/><path d="M18.92,13l-3.061,0c0.083,-0.321 0.141,-0.653 0.141,-1c0,-2.209 -1.791,-4 -4,-4c-2.209,0 -4,1.791 -4,4c0,1.105 0.448,2.105 1.172,2.828c1.014,1.015 4.057,4.058 4.057,4.058c2.955,-0.525 5.263,-2.899 5.691,-5.886Z" style="fill:#4d4d4d;fill-rule:nonzero;"/><path d="M22,13l-10,0c-0.553,0 -1,-0.448 -1,-1c0,-0.552 0.447,-1 1,-1l10,0c0.553,0 1,0.448 1,1c0,0.552 -0.447,1 -1,1Z" style="fill:#b3b3b3;fill-rule:nonzero;"/><path d="M11.948,11.25l10.104,0c0.409,0 0.776,0.247 0.935,0.592c-0.08,-0.471 -0.492,-0.842 -0.987,-0.842l-10,0c-0.495,0 -0.9,0.33 -0.98,0.801c0.159,-0.345 0.519,-0.551 0.928,-0.551Z" style="fill:#fff;fill-opacity:0.2;fill-rule:nonzero;"/><path d="M23,12c0,0.552 -0.447,1 -1,1l-3.08,0c-0.428,2.988 -2.737,5.362 -5.693,5.886l3.935,3.946c4.04,-1.931 6.838,-6.056 6.838,-10.832l-1,0Z" style="fill:#666;fill-opacity:0.5;fill-rule:nonzero;"/><path d="M12,5c-3.866,0 -7,3.134 -7,7c0,0.042 -0.001,0.069 0,0.111c0.067,-3.808 3.176,-6.861 7,-6.861c2.828,0 4.841,1.701 4.841,1.701c-1.257,-1.198 -2.968,-1.951 -4.841,-1.951Z" style="fill-opacity:0.1;fill-rule:nonzero;"/><circle id="XMLID_4_" cx="12" cy="12" r="12" style="fill:url(#_Linear1);"/></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(21.7566,10.1453,-10.1453,21.7566,1.12171,6.92737)"><stop offset="0" style="stop-color:#fff;stop-opacity:0.2"/><stop offset="1" style="stop-color:#fff;stop-opacity:0"/></linearGradient></defs></svg>
|
After Width: | Height: | Size: 3.6 KiB |
1
Frontend/src/pages/login/icons/BackupCode.svg
Normal file
1
Frontend/src/pages/login/icons/BackupCode.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><path d="M20.562,9.105c0,-0.853 -0.692,-1.544 -1.544,-1.544l-14.036,0c-0.852,0 -1.544,0.691 -1.544,1.544l0,12.351c0,0.852 0.692,1.544 1.544,1.544l14.036,0c0.852,0 1.544,-0.692 1.544,-1.544l0,-12.351Z" style="fill:none;stroke:#000;stroke-width:1.5px;"/><circle cx="12" cy="15.3" r="1.5"/><path d="M16.646,4.28c0,-1.81 -1.47,-3.28 -3.28,-3.28l-2.732,0c-1.81,0 -3.28,1.47 -3.28,3.28l0,3.281l9.292,0l0,-3.281Z" style="fill:none;stroke:#000;stroke-width:1.5px;"/></svg>
|
After Width: | Height: | Size: 927 B |
15
Frontend/src/pages/login/icons/Icon.svelte
Normal file
15
Frontend/src/pages/login/icons/Icon.svelte
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<script>
|
||||||
|
export let icon_name;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#if icon_name === "SecurityKey"}
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18,7.692c0,-0.925 -0.751,-1.675 -1.675,-1.675l-14.65,0c-0.924,0 -1.675,0.75 -1.675,1.675l0,8.616c0,0.925 0.751,1.675 1.675,1.675l14.65,0c0.924,0 1.675,-0.75 1.675,-1.675l0,-8.616Z" style="fill:#4d4d4d;"/><rect x="18" y="8.011" width="6" height="7.978" style="fill:#4d4d4d;"/><rect x="18" y="10.644" width="4.8" height="1.231" style="fill:#b3b3b3;"/><rect x="18" y="12.229" width="4.8" height="1.164" style="fill:#b3b3b3;"/><rect x="18" y="9.008" width="5.25" height="1.231" style="fill:#b3b3b3;"/><rect x="18" y="13.794" width="5.25" height="1.197" style="fill:#b3b3b3;"/></svg>
|
||||||
|
{:else if icon_name === "Authenticator"}
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><g><path d="M18.5,12c0,3.59 -2.91,6.5 -6.5,6.5c-3.59,0 -6.5,-2.91 -6.5,-6.5c0,-3.59 2.91,-6.5 6.5,-6.5c1.729,0 3.295,0.679 4.46,1.78l4.169,-3.599c-2.184,-2.265 -5.242,-3.681 -8.629,-3.681c-6.617,0 -12,5.383 -12,12c0,6.617 5.383,12 12,12c6.617,0 12,-5.383 12,-12l-5.5,0Z" style="fill:#999;fill-rule:nonzero;"/><circle id="XMLID_1331_" cx="12" cy="12" r="12" style="fill:#808080;"/><path d="M19,12c0,3.866 -3.134,7 -7,7c-3.866,0 -7,-3.134 -7,-7c0,-3.866 3.134,-7 7,-7c1.88,0 3.583,0.745 4.841,1.951l3.788,-3.27c-2.184,-2.265 -5.242,-3.681 -8.629,-3.681c-6.617,0 -12,5.383 -12,12c0,6.617 5.383,12 12,12c6.617,0 12,-5.383 12,-12l-5,0Z" style="fill:#999;fill-rule:nonzero;"/><circle cx="12" cy="2.5" r="1" style="fill:#b3b3b3;"/><circle cx="12" cy="21.5" r="1" style="fill:#b3b3b3;"/><circle cx="2.5" cy="12" r="1" style="fill:#b3b3b3;"/><path d="M4.575,18.01c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><path d="M18.01,18.01c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><path d="M4.575,4.575c0.391,-0.39 1.024,-0.39 1.415,0c0.39,0.391 0.39,1.024 0,1.415c-0.391,0.39 -1.024,0.39 -1.415,0c-0.39,-0.391 -0.39,-1.024 0,-1.415Z" style="fill:#b3b3b3;"/><circle id="XMLID_1329_" cx="12" cy="12" r="6" style="fill:#808080;"/><circle id="XMLID_1330_" cx="12" cy="12" r="7" style="fill:#808080;"/><path d="M19,12.25c0,-0.042 -0.006,-0.083 -0.006,-0.125c-0.068,3.808 -3.17,6.875 -6.994,6.875c-3.824,0 -6.933,-3.067 -7,-6.875c-0.001,0.042 0,0.083 0,0.125c0,3.866 3.134,7 7,7c3.866,0 7,-3.134 7,-7Z" style="fill:#fff;fill-opacity:0.2;fill-rule:nonzero;"/><path d="M18.92,13l-3.061,0c0.083,-0.321 0.141,-0.653 0.141,-1c0,-2.209 -1.791,-4 -4,-4c-2.209,0 -4,1.791 -4,4c0,1.105 0.448,2.105 1.172,2.828c1.014,1.015 4.057,4.058 4.057,4.058c2.955,-0.525 5.263,-2.899 5.691,-5.886Z" style="fill:#4d4d4d;fill-rule:nonzero;"/><path d="M22,13l-10,0c-0.553,0 -1,-0.448 -1,-1c0,-0.552 0.447,-1 1,-1l10,0c0.553,0 1,0.448 1,1c0,0.552 -0.447,1 -1,1Z" style="fill:#b3b3b3;fill-rule:nonzero;"/><path d="M11.948,11.25l10.104,0c0.409,0 0.776,0.247 0.935,0.592c-0.08,-0.471 -0.492,-0.842 -0.987,-0.842l-10,0c-0.495,0 -0.9,0.33 -0.98,0.801c0.159,-0.345 0.519,-0.551 0.928,-0.551Z" style="fill:#fff;fill-opacity:0.2;fill-rule:nonzero;"/><path d="M23,12c0,0.552 -0.447,1 -1,1l-3.08,0c-0.428,2.988 -2.737,5.362 -5.693,5.886l3.935,3.946c4.04,-1.931 6.838,-6.056 6.838,-10.832l-1,0Z" style="fill:#666;fill-opacity:0.5;fill-rule:nonzero;"/><path d="M12,5c-3.866,0 -7,3.134 -7,7c0,0.042 -0.001,0.069 0,0.111c0.067,-3.808 3.176,-6.861 7,-6.861c2.828,0 4.841,1.701 4.841,1.701c-1.257,-1.198 -2.968,-1.951 -4.841,-1.951Z" style="fill-opacity:0.1;fill-rule:nonzero;"/><circle id="XMLID_4_" cx="12" cy="12" r="12" style="fill:url(#_Linear1);"/></g><defs><linearGradient id="_Linear1" x1="0" y1="0" x2="1" y2="0" gradientUnits="userSpaceOnUse" gradientTransform="matrix(21.7566,10.1453,-10.1453,21.7566,1.12171,6.92737)"><stop offset="0" style="stop-color:#fff;stop-opacity:0.2"/><stop offset="1" style="stop-color:#fff;stop-opacity:0"/></linearGradient></defs></svg>
|
||||||
|
{:else if icon_name === "BackupCode"}
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:1.5;"><path d="M20.562,9.105c0,-0.853 -0.692,-1.544 -1.544,-1.544l-14.036,0c-0.852,0 -1.544,0.691 -1.544,1.544l0,12.351c0,0.852 0.692,1.544 1.544,1.544l14.036,0c0.852,0 1.544,-0.692 1.544,-1.544l0,-12.351Z" style="fill:none;stroke:#000;stroke-width:1.5px;"/><circle cx="12" cy="15.3" r="1.5"/><path d="M16.646,4.28c0,-1.81 -1.47,-3.28 -3.28,-3.28l-2.732,0c-1.81,0 -3.28,1.47 -3.28,3.28l0,3.281l9.292,0l0,-3.281Z" style="fill:none;stroke:#000;stroke-width:1.5px;"/></svg>
|
||||||
|
{:else if icon_name === "AppPush"}
|
||||||
|
<svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18.617,1.72c0,-0.949 -0.771,-1.72 -1.721,-1.72l-9.792,0c-0.95,0 -1.721,0.771 -1.721,1.72l0,20.56c0,0.949 0.771,1.72 1.721,1.72l9.792,0c0.95,0 1.721,-0.771 1.721,-1.72l0,-20.56Z" style="fill:#4d4d4d;"/><rect x="6" y="3" width="12" height="18" style="fill:#b3b3b3;"/><path d="M14,1.5c0,-0.129 -0.105,-0.233 -0.233,-0.233l-3.534,0c-0.128,0 -0.233,0.104 -0.233,0.233c0,0.129 0.105,0.233 0.233,0.233l3.534,0c0.128,0 0.233,-0.104 0.233,-0.233Z" style="fill:#b3b3b3;"/><ellipse cx="12" cy="22.5" rx="0.983" ry="1" style="fill:#b3b3b3;"/></svg>
|
||||||
|
{:else}
|
||||||
|
ERR
|
||||||
|
{/if}
|
1
Frontend/src/pages/login/icons/SecurityKey.svg
Normal file
1
Frontend/src/pages/login/icons/SecurityKey.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:1.41421;"><path d="M18,7.692c0,-0.925 -0.751,-1.675 -1.675,-1.675l-14.65,0c-0.924,0 -1.675,0.75 -1.675,1.675l0,8.616c0,0.925 0.751,1.675 1.675,1.675l14.65,0c0.924,0 1.675,-0.75 1.675,-1.675l0,-8.616Z" style="fill:#4d4d4d;"/><rect x="18" y="8.011" width="6" height="7.978" style="fill:#4d4d4d;"/><rect x="18" y="10.644" width="4.8" height="1.231" style="fill:#b3b3b3;"/><rect x="18" y="12.229" width="4.8" height="1.164" style="fill:#b3b3b3;"/><rect x="18" y="9.008" width="5.25" height="1.231" style="fill:#b3b3b3;"/><rect x="18" y="13.794" width="5.25" height="1.197" style="fill:#b3b3b3;"/></svg>
|
After Width: | Height: | Size: 1.0 KiB |
5
Frontend/src/pages/login/main.ts
Normal file
5
Frontend/src/pages/login/main.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import App from "./App.svelte";
|
||||||
|
|
||||||
|
new App({
|
||||||
|
target: document.body,
|
||||||
|
});
|
33
Frontend/src/pages/login/twofactors/codeInput.svelte
Normal file
33
Frontend/src/pages/login/twofactors/codeInput.svelte
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<script>
|
||||||
|
import Cleave from "cleave.js";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
|
||||||
|
export let error;
|
||||||
|
// export let label;
|
||||||
|
export let value;
|
||||||
|
export let length = 6;
|
||||||
|
|
||||||
|
let input;
|
||||||
|
onMount(() => {
|
||||||
|
const cleaveCustom = new Cleave(input, {
|
||||||
|
blocks: [length / 2, length / 2],
|
||||||
|
delimiter: " ",
|
||||||
|
numericOnly: true,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="floating group">
|
||||||
|
<input id="noasidhglk" bind:this={input} autofocus bind:value />
|
||||||
|
<span class="highlight" />
|
||||||
|
<span class="bar" />
|
||||||
|
<label for="noasidhglk">Code</label>
|
||||||
|
<div class="error" style={!error ? 'display: none;' : ''}>{error}</div>
|
||||||
|
</div>
|
50
Frontend/src/pages/login/twofactors/otc.svelte
Normal file
50
Frontend/src/pages/login/twofactors/otc.svelte
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
<script>
|
||||||
|
import ToList from "./toList.svelte";
|
||||||
|
import Api from "../api.ts";
|
||||||
|
import CodeInput from "./codeInput.svelte";
|
||||||
|
|
||||||
|
let error = "";
|
||||||
|
let code = "";
|
||||||
|
export let finish;
|
||||||
|
export let id;
|
||||||
|
|
||||||
|
export let otc = false;
|
||||||
|
let title = otc ? "One Time Code (OTC)" : "Backup Code";
|
||||||
|
let length = otc ? 6 : 8;
|
||||||
|
|
||||||
|
async function sendCode() {
|
||||||
|
let c = code.replace(/\s+/g, "");
|
||||||
|
if (c.length < length) {
|
||||||
|
error = `Code must be ${length} digits long!`;
|
||||||
|
} else {
|
||||||
|
error = "";
|
||||||
|
let res;
|
||||||
|
if (otc) res = await Api.sendOTC(id, c);
|
||||||
|
else res = await Api.sendBackup(id, c);
|
||||||
|
if (res.error) error = res.error;
|
||||||
|
else finish(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-next {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: auto;
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h3>{title}</h3>
|
||||||
|
|
||||||
|
<CodeInput bind:value={code} label="Code" {error} {length} />
|
||||||
|
|
||||||
|
<div class="actions">
|
||||||
|
<ToList {finish} />
|
||||||
|
<button class="btn btn-primary btn-next" on:click={sendCode}> Send </button>
|
||||||
|
</div>
|
389
Frontend/src/pages/login/twofactors/push.svelte
Normal file
389
Frontend/src/pages/login/twofactors/push.svelte
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
<script>
|
||||||
|
import ToList from "./toList.svelte";
|
||||||
|
|
||||||
|
let error = "";
|
||||||
|
let code = "";
|
||||||
|
export let device = "Handy01";
|
||||||
|
// export let deviceId = "";
|
||||||
|
|
||||||
|
export let finish;
|
||||||
|
|
||||||
|
async function requestPush() {
|
||||||
|
// Push Request
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows8 {
|
||||||
|
position: relative;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
margin: 2rem auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows8 .wBall {
|
||||||
|
position: absolute;
|
||||||
|
width: 53px;
|
||||||
|
height: 53px;
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(225deg);
|
||||||
|
-o-transform: rotate(225deg);
|
||||||
|
-ms-transform: rotate(225deg);
|
||||||
|
-webkit-transform: rotate(225deg);
|
||||||
|
-moz-transform: rotate(225deg);
|
||||||
|
animation: orbit 5.7425s infinite;
|
||||||
|
-o-animation: orbit 5.7425s infinite;
|
||||||
|
-ms-animation: orbit 5.7425s infinite;
|
||||||
|
-webkit-animation: orbit 5.7425s infinite;
|
||||||
|
-moz-animation: orbit 5.7425s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows8 .wBall .wInnerBall {
|
||||||
|
position: absolute;
|
||||||
|
width: 7px;
|
||||||
|
height: 7px;
|
||||||
|
background: rgb(0, 140, 255);
|
||||||
|
left: 0px;
|
||||||
|
top: 0px;
|
||||||
|
border-radius: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows8 #wBall_1 {
|
||||||
|
animation-delay: 1.256s;
|
||||||
|
-o-animation-delay: 1.256s;
|
||||||
|
-ms-animation-delay: 1.256s;
|
||||||
|
-webkit-animation-delay: 1.256s;
|
||||||
|
-moz-animation-delay: 1.256s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows8 #wBall_2 {
|
||||||
|
animation-delay: 0.243s;
|
||||||
|
-o-animation-delay: 0.243s;
|
||||||
|
-ms-animation-delay: 0.243s;
|
||||||
|
-webkit-animation-delay: 0.243s;
|
||||||
|
-moz-animation-delay: 0.243s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows8 #wBall_3 {
|
||||||
|
animation-delay: 0.5065s;
|
||||||
|
-o-animation-delay: 0.5065s;
|
||||||
|
-ms-animation-delay: 0.5065s;
|
||||||
|
-webkit-animation-delay: 0.5065s;
|
||||||
|
-moz-animation-delay: 0.5065s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows8 #wBall_4 {
|
||||||
|
animation-delay: 0.7495s;
|
||||||
|
-o-animation-delay: 0.7495s;
|
||||||
|
-ms-animation-delay: 0.7495s;
|
||||||
|
-webkit-animation-delay: 0.7495s;
|
||||||
|
-moz-animation-delay: 0.7495s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.windows8 #wBall_5 {
|
||||||
|
animation-delay: 1.003s;
|
||||||
|
-o-animation-delay: 1.003s;
|
||||||
|
-ms-animation-delay: 1.003s;
|
||||||
|
-webkit-animation-delay: 1.003s;
|
||||||
|
-moz-animation-delay: 1.003s;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes orbit {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 99;
|
||||||
|
transform: rotate(180deg);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
7% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(300deg);
|
||||||
|
animation-timing-function: linear;
|
||||||
|
origin: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(410deg);
|
||||||
|
animation-timing-function: ease-in-out;
|
||||||
|
origin: 7%;
|
||||||
|
}
|
||||||
|
|
||||||
|
39% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(645deg);
|
||||||
|
animation-timing-function: linear;
|
||||||
|
origin: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(770deg);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
origin: 39%;
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: rotate(900deg);
|
||||||
|
animation-timing-function: ease-out;
|
||||||
|
origin: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
76% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-o-keyframes orbit {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 99;
|
||||||
|
-o-transform: rotate(180deg);
|
||||||
|
-o-animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
7% {
|
||||||
|
opacity: 1;
|
||||||
|
-o-transform: rotate(300deg);
|
||||||
|
-o-animation-timing-function: linear;
|
||||||
|
-o-origin: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
opacity: 1;
|
||||||
|
-o-transform: rotate(410deg);
|
||||||
|
-o-animation-timing-function: ease-in-out;
|
||||||
|
-o-origin: 7%;
|
||||||
|
}
|
||||||
|
|
||||||
|
39% {
|
||||||
|
opacity: 1;
|
||||||
|
-o-transform: rotate(645deg);
|
||||||
|
-o-animation-timing-function: linear;
|
||||||
|
-o-origin: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
opacity: 1;
|
||||||
|
-o-transform: rotate(770deg);
|
||||||
|
-o-animation-timing-function: ease-out;
|
||||||
|
-o-origin: 39%;
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
opacity: 1;
|
||||||
|
-o-transform: rotate(900deg);
|
||||||
|
-o-animation-timing-function: ease-out;
|
||||||
|
-o-origin: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
76% {
|
||||||
|
opacity: 0;
|
||||||
|
-o-transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-o-transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-ms-keyframes orbit {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 99;
|
||||||
|
-ms-transform: rotate(180deg);
|
||||||
|
-ms-animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
7% {
|
||||||
|
opacity: 1;
|
||||||
|
-ms-transform: rotate(300deg);
|
||||||
|
-ms-animation-timing-function: linear;
|
||||||
|
-ms-origin: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
opacity: 1;
|
||||||
|
-ms-transform: rotate(410deg);
|
||||||
|
-ms-animation-timing-function: ease-in-out;
|
||||||
|
-ms-origin: 7%;
|
||||||
|
}
|
||||||
|
|
||||||
|
39% {
|
||||||
|
opacity: 1;
|
||||||
|
-ms-transform: rotate(645deg);
|
||||||
|
-ms-animation-timing-function: linear;
|
||||||
|
-ms-origin: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
opacity: 1;
|
||||||
|
-ms-transform: rotate(770deg);
|
||||||
|
-ms-animation-timing-function: ease-out;
|
||||||
|
-ms-origin: 39%;
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
opacity: 1;
|
||||||
|
-ms-transform: rotate(900deg);
|
||||||
|
-ms-animation-timing-function: ease-out;
|
||||||
|
-ms-origin: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
76% {
|
||||||
|
opacity: 0;
|
||||||
|
-ms-transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-ms-transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes orbit {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 99;
|
||||||
|
-webkit-transform: rotate(180deg);
|
||||||
|
-webkit-animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
7% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: rotate(300deg);
|
||||||
|
-webkit-animation-timing-function: linear;
|
||||||
|
-webkit-origin: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: rotate(410deg);
|
||||||
|
-webkit-animation-timing-function: ease-in-out;
|
||||||
|
-webkit-origin: 7%;
|
||||||
|
}
|
||||||
|
|
||||||
|
39% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: rotate(645deg);
|
||||||
|
-webkit-animation-timing-function: linear;
|
||||||
|
-webkit-origin: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: rotate(770deg);
|
||||||
|
-webkit-animation-timing-function: ease-out;
|
||||||
|
-webkit-origin: 39%;
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
opacity: 1;
|
||||||
|
-webkit-transform: rotate(900deg);
|
||||||
|
-webkit-animation-timing-function: ease-out;
|
||||||
|
-webkit-origin: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
76% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-webkit-transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-moz-keyframes orbit {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
z-index: 99;
|
||||||
|
-moz-transform: rotate(180deg);
|
||||||
|
-moz-animation-timing-function: ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
7% {
|
||||||
|
opacity: 1;
|
||||||
|
-moz-transform: rotate(300deg);
|
||||||
|
-moz-animation-timing-function: linear;
|
||||||
|
-moz-origin: 0%;
|
||||||
|
}
|
||||||
|
|
||||||
|
30% {
|
||||||
|
opacity: 1;
|
||||||
|
-moz-transform: rotate(410deg);
|
||||||
|
-moz-animation-timing-function: ease-in-out;
|
||||||
|
-moz-origin: 7%;
|
||||||
|
}
|
||||||
|
|
||||||
|
39% {
|
||||||
|
opacity: 1;
|
||||||
|
-moz-transform: rotate(645deg);
|
||||||
|
-moz-animation-timing-function: linear;
|
||||||
|
-moz-origin: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
70% {
|
||||||
|
opacity: 1;
|
||||||
|
-moz-transform: rotate(770deg);
|
||||||
|
-moz-animation-timing-function: ease-out;
|
||||||
|
-moz-origin: 39%;
|
||||||
|
}
|
||||||
|
|
||||||
|
75% {
|
||||||
|
opacity: 1;
|
||||||
|
-moz-transform: rotate(900deg);
|
||||||
|
-moz-animation-timing-function: ease-out;
|
||||||
|
-moz-origin: 70%;
|
||||||
|
}
|
||||||
|
|
||||||
|
76% {
|
||||||
|
opacity: 0;
|
||||||
|
-moz-transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
-moz-transform: rotate(900deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h3>SMS</h3>
|
||||||
|
|
||||||
|
<p>A code was sent to your Device <b>{device}</b></p>
|
||||||
|
|
||||||
|
<div class="windows8">
|
||||||
|
<div class="wBall" id="wBall_1">
|
||||||
|
<div class="wInnerBall" />
|
||||||
|
</div>
|
||||||
|
<div class="wBall" id="wBall_2">
|
||||||
|
<div class="wInnerBall" />
|
||||||
|
</div>
|
||||||
|
<div class="wBall" id="wBall_3">
|
||||||
|
<div class="wInnerBall" />
|
||||||
|
</div>
|
||||||
|
<div class="wBall" id="wBall_4">
|
||||||
|
<div class="wInnerBall" />
|
||||||
|
</div>
|
||||||
|
<div class="wBall" id="wBall_5">
|
||||||
|
<div class="wInnerBall" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="error">{error}</div>
|
||||||
|
<ToList {finish} />
|
49
Frontend/src/pages/login/twofactors/sms.svelte
Normal file
49
Frontend/src/pages/login/twofactors/sms.svelte
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<script>
|
||||||
|
import ToList from "./toList.svelte";
|
||||||
|
|
||||||
|
const states = {
|
||||||
|
approve: 1,
|
||||||
|
enter: 2,
|
||||||
|
};
|
||||||
|
let state = states.approve;
|
||||||
|
|
||||||
|
let error = "";
|
||||||
|
let code = "";
|
||||||
|
export let number = "+4915...320";
|
||||||
|
//export let finish;
|
||||||
|
|
||||||
|
function validateCode() {}
|
||||||
|
|
||||||
|
function sendCode() {
|
||||||
|
// Send request to Server
|
||||||
|
state = states.enter;
|
||||||
|
//finish()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--error: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h3>SMS</h3>
|
||||||
|
{#if state === states.approve}
|
||||||
|
<p>Send SMS to {number}</p>
|
||||||
|
<button class="btn btn-primary" on:click={sendCode}>Send</button>
|
||||||
|
{:else}
|
||||||
|
<p>A code was sent to you. Please enter</p>
|
||||||
|
<input type="number" placeholder="Code" bind:value={code} />
|
||||||
|
<button class="btn btn-primary" on:click={validateCode}>Send</button>
|
||||||
|
<br />
|
||||||
|
<a href="# " on:click|preventDefault={() => (state = states.approve)}>
|
||||||
|
Not received?
|
||||||
|
</a>
|
||||||
|
{/if}
|
||||||
|
<div class="error">{error}</div>
|
||||||
|
|
||||||
|
<ToList {finish} />
|
17
Frontend/src/pages/login/twofactors/toList.svelte
Normal file
17
Frontend/src/pages/login/twofactors/toList.svelte
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<script>
|
||||||
|
export let finish = () => {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
a {
|
||||||
|
color: var(--primary);
|
||||||
|
text-decoration: none;
|
||||||
|
margin-right: 1rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="# " on:click={evt => evt.preventDefault() || finish(false)}>
|
||||||
|
Choose another Method
|
||||||
|
</a>
|
||||||
|
</p>
|
69
Frontend/src/pages/login/twofactors/u2f.svelte
Normal file
69
Frontend/src/pages/login/twofactors/u2f.svelte
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
<script>
|
||||||
|
import ToList from "./toList.svelte";
|
||||||
|
|
||||||
|
export let finish;
|
||||||
|
|
||||||
|
const states = {
|
||||||
|
getChallenge: 0,
|
||||||
|
requestUser: 1,
|
||||||
|
sendChallenge: 2,
|
||||||
|
error: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
let state = states.getChallenge;
|
||||||
|
|
||||||
|
let error = "";
|
||||||
|
|
||||||
|
const onError = err => {
|
||||||
|
state = states.error;
|
||||||
|
error = err.message;
|
||||||
|
};
|
||||||
|
|
||||||
|
let challenge;
|
||||||
|
|
||||||
|
async function requestUser() {
|
||||||
|
state = states.requestUser;
|
||||||
|
let res = await window.navigator.credentials.get({
|
||||||
|
publicKey: challenge
|
||||||
|
});
|
||||||
|
state = states.sendChallenge();
|
||||||
|
let r = res.response;
|
||||||
|
let data = encode({
|
||||||
|
authenticatorData: r.authenticatorData,
|
||||||
|
clientDataJSON: r.clientDataJSON,
|
||||||
|
signature: r.signature,
|
||||||
|
userHandle: r.userHandle
|
||||||
|
});
|
||||||
|
let { success } = fetch("https://localhost:8444/auth", {
|
||||||
|
body: data,
|
||||||
|
method: "POST"
|
||||||
|
}).then(res => res.json());
|
||||||
|
if (success) {
|
||||||
|
finish(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getChallenge() {
|
||||||
|
state = states.getChallenge;
|
||||||
|
challenge = await fetch("https://localhost:8444/auth")
|
||||||
|
.then(res => res.arrayBuffer())
|
||||||
|
.then(data => decode(MessagePack.Buffer.from(data)));
|
||||||
|
|
||||||
|
requestUser().catch(onError);
|
||||||
|
}
|
||||||
|
getChallenge().catch(onError);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--error: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<h3>U2F Security Key</h3>
|
||||||
|
<h4>This Method is currently not supported. Please choose another one!</h4>
|
||||||
|
<ToList {finish} />
|
59
Frontend/src/pages/popup/App.svelte
Normal file
59
Frontend/src/pages/popup/App.svelte
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import HoveringContentBox from "../../components/HoveringContentBox.svelte";
|
||||||
|
import Theme from "../../components/theme/Theme.svelte";
|
||||||
|
|
||||||
|
export let loading = true;
|
||||||
|
export let appName = "";
|
||||||
|
export let permissions: any[] = [];
|
||||||
|
export let accept: () => void;
|
||||||
|
|
||||||
|
const base_perm = {
|
||||||
|
name: "Access Profile",
|
||||||
|
description:
|
||||||
|
"Access your identity and some basic informations like your username",
|
||||||
|
};
|
||||||
|
|
||||||
|
$: view_perms = [base_perm, ...permissions];
|
||||||
|
|
||||||
|
$: console.log({ loading, appName, permissions, accept });
|
||||||
|
|
||||||
|
function deny() {
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Theme dark={false}>
|
||||||
|
<HoveringContentBox title="Authorize" {loading} hide>
|
||||||
|
<div class="title margin">
|
||||||
|
<h2 style="font-weight: normal">
|
||||||
|
Grant
|
||||||
|
<span id="hostname" style="font-weight: bold;">{appName}</span>
|
||||||
|
the following permissions?
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul class="list list-divider">
|
||||||
|
{#each view_perms as permission (permission._íd)}
|
||||||
|
<li class="permission">
|
||||||
|
<h3>{permission.name}</h3>
|
||||||
|
<p>{permission.description}</p>
|
||||||
|
</li>
|
||||||
|
{/each}
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div style="text-align: right;">
|
||||||
|
<button class="btn btn-primary" on:click={accept}>Allow</button>
|
||||||
|
<button class="btn btn-primary" on:click={deny}>Deny</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</HoveringContentBox>
|
||||||
|
</Theme>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.permission > h3 {
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission > p {
|
||||||
|
}
|
||||||
|
</style>
|
146
Frontend/src/pages/popup/main.ts
Normal file
146
Frontend/src/pages/popup/main.ts
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
import "../../components/theme";
|
||||||
|
import App from "./App.svelte";
|
||||||
|
import request from "../../helper/request";
|
||||||
|
|
||||||
|
interface IPermission {
|
||||||
|
_id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let loading = true;
|
||||||
|
let appName: string;
|
||||||
|
let permissions: IPermission[] = [];
|
||||||
|
let accept: () => void;
|
||||||
|
|
||||||
|
const app = new App({
|
||||||
|
target: document.body,
|
||||||
|
props: { loading, accept },
|
||||||
|
});
|
||||||
|
|
||||||
|
const setLoading = (_loading: boolean) => {
|
||||||
|
loading = _loading;
|
||||||
|
app.$set({ loading });
|
||||||
|
};
|
||||||
|
|
||||||
|
const setAppName = (_appName: string) => {
|
||||||
|
appName = _appName;
|
||||||
|
app.$set({ appName });
|
||||||
|
};
|
||||||
|
|
||||||
|
const setPermissions = (_permissions: IPermission[]) => {
|
||||||
|
permissions = _permissions;
|
||||||
|
app.$set({ permissions });
|
||||||
|
};
|
||||||
|
|
||||||
|
const setAccept = (_accept: () => void) => {
|
||||||
|
accept = _accept;
|
||||||
|
app.$set({ accept });
|
||||||
|
};
|
||||||
|
|
||||||
|
async function getJWT(client_id: string, origin: string) {
|
||||||
|
origin = encodeURIComponent(origin);
|
||||||
|
client_id = encodeURIComponent(client_id);
|
||||||
|
|
||||||
|
const res = await request(`/api/user/oauth/jwt`, {
|
||||||
|
client_id,
|
||||||
|
origin,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getRefreshToken(
|
||||||
|
client_id: string,
|
||||||
|
origin: string,
|
||||||
|
permissions: string[]
|
||||||
|
) {
|
||||||
|
origin = encodeURIComponent(origin);
|
||||||
|
client_id = encodeURIComponent(client_id);
|
||||||
|
const perm = permissions.map((e) => encodeURIComponent(e)).join(",");
|
||||||
|
|
||||||
|
const res = await request(`/api/user/oauth/refresh_token`, {
|
||||||
|
client_id,
|
||||||
|
origin,
|
||||||
|
permissions: perm,
|
||||||
|
});
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
let started = false;
|
||||||
|
async function onMessage(msg: MessageEvent<any>) {
|
||||||
|
const sendResponse = (data: any) => {
|
||||||
|
try {
|
||||||
|
console.log("Sending response:", data);
|
||||||
|
(msg.source.postMessage as any)(data, msg.origin);
|
||||||
|
} catch (err) {
|
||||||
|
alert("Something went wrong, please try again later!");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
console.log("Received message", msg, started);
|
||||||
|
if (!started) {
|
||||||
|
started = true;
|
||||||
|
const url = new URL(msg.origin);
|
||||||
|
setAppName(url.hostname);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!msg.data.type || msg.data.type === "jwt") {
|
||||||
|
console.log("JWT Request");
|
||||||
|
await new Promise<void>((yes) => {
|
||||||
|
console.log("Await user acceptance");
|
||||||
|
setLoading(false);
|
||||||
|
setAccept(yes);
|
||||||
|
});
|
||||||
|
console.log("User has accepted");
|
||||||
|
const res = await getJWT(msg.data.client_id, url.hostname);
|
||||||
|
sendResponse(res);
|
||||||
|
} else if (msg.data.type === "refresh") {
|
||||||
|
console.log("RefreshToken Request");
|
||||||
|
let permissions = msg.data.permissions || [];
|
||||||
|
let permissions_resolved = [];
|
||||||
|
|
||||||
|
if (permissions.length > 0) {
|
||||||
|
permissions_resolved = await request(
|
||||||
|
"/api/user/oauth/permissions",
|
||||||
|
{
|
||||||
|
client_id: msg.data.client_id,
|
||||||
|
origin: url.hostname,
|
||||||
|
permissions: permissions.join(","),
|
||||||
|
}
|
||||||
|
).then(({ permissions }) => permissions);
|
||||||
|
}
|
||||||
|
|
||||||
|
await new Promise<void>((yes) => {
|
||||||
|
console.log("Await user acceptance");
|
||||||
|
setLoading(false);
|
||||||
|
setPermissions(permissions_resolved);
|
||||||
|
setAccept(yes);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("User has accepted");
|
||||||
|
|
||||||
|
const res = await getRefreshToken(
|
||||||
|
msg.data.client_id,
|
||||||
|
url.hostname,
|
||||||
|
permissions
|
||||||
|
);
|
||||||
|
sendResponse(res);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
sendResponse({ error: true, message: err.message });
|
||||||
|
}
|
||||||
|
window.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!started) {
|
||||||
|
console.log("No authentication request received!");
|
||||||
|
alert(
|
||||||
|
"The site requesting the login does not respond. Please try again later"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
window.addEventListener("message", onMessage);
|
207
Frontend/src/pages/user/App.svelte
Normal file
207
Frontend/src/pages/user/App.svelte
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
<script>
|
||||||
|
import AccountPage from "./Pages/Account.svelte";
|
||||||
|
import SecurityPage from "./Pages/Security.svelte";
|
||||||
|
import { slide, fade } from "svelte/transition";
|
||||||
|
|
||||||
|
const pages = [
|
||||||
|
{
|
||||||
|
id: "account",
|
||||||
|
title: "Account",
|
||||||
|
icon: "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB4bWxuczpza2V0Y2g9Imh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaC9ucyIgdmlld0JveD0iMCAwIDUwMCA1MDAiIHZlcnNpb249IjEuMSIgeD0iMHB4IiB5PSIwcHgiPjx0aXRsZT4wMSBAZnVsbHdpZHRoPC90aXRsZT48ZGVzYz5DcmVhdGVkIHdpdGggU2tldGNoLjwvZGVzYz48ZyBzdHJva2U9Im5vbmUiIHN0cm9rZS13aWR0aD0iMSIgZmlsbD0ibm9uZSIgZmlsbC1ydWxlPSJldmVub2RkIiBza2V0Y2g6dHlwZT0iTVNQYWdlIj48ZyBza2V0Y2g6dHlwZT0iTVNBcnRib2FyZEdyb3VwIiBmaWxsPSIjMDAwMDAwIj48cGF0aCBkPSJNNDU3LjUsMjUwIEM0NTcuNSwxMzUuOTUzMTk5IDM2NS4wNDY4MDEsNDMuNSAyNTEsNDMuNSBDMTM2Ljk1MzE5OSw0My41IDQ0LjUsMTM1Ljk1MzE5OSA0NC41LDI1MCBDNDQuNSwzNjQuMDQ2ODAxIDEzNi45NTMxOTksNDU2LjUgMjUxLDQ1Ni41IEMzNjUuMDQ2ODAxLDQ1Ni41IDQ1Ny41LDM2NC4wNDY4MDEgNDU3LjUsMjUwIFogTTU3LjUsMjUwIEM1Ny41LDE0My4xMzI5MDEgMTQ0LjEzMjkwMSw1Ni41IDI1MSw1Ni41IEMzNTcuODY3MDk5LDU2LjUgNDQ0LjUsMTQzLjEzMjkwMSA0NDQuNSwyNTAgQzQ0NC41LDM1Ni44NjcwOTkgMzU3Ljg2NzA5OSw0NDMuNSAyNTEsNDQzLjUgQzE0NC4xMzI5MDEsNDQzLjUgNTcuNSwzNTYuODY3MDk5IDU3LjUsMjUwIFoiIHNrZXRjaDp0eXBlPSJNU1NoYXBlR3JvdXAiPjwvcGF0aD48cGF0aCBkPSJNMjUxLjUsMjUyLjkzMzk2MiBDMTk2Ljg1NDE5LDI1Mi45MzM5NjIgMTUyLjUsMjk2LjM2MDgwOSAxNTIuNSwzNTAgQzE1Mi41LDM1My41ODk4NTEgMTU1LjQxMDE0OSwzNTYuNSAxNTksMzU2LjUgTDM0NCwzNTYuNSBDMzQ3LjU4OTg1MSwzNTYuNSAzNTAuNSwzNTMuNTg5ODUxIDM1MC41LDM1MCBDMzUwLjUsMjk2LjM2MDgwOSAzMDYuMTQ1ODEsMjUyLjkzMzk2MiAyNTEuNSwyNTIuOTMzOTYyIFogTTE2NS41LDM0My41MDAwMDEgQzE2NS41LDMwMy42MDI3MDggMjAzLjk3MzEzMSwyNjUuOTMzOTYyIDI1MS41LDI2NS45MzM5NjIgQzI5OS4wMjY4NjksMjY1LjkzMzk2MiAzMzcuNSwzMDMuNjAyNzA4IDMzNy40OTk5OTcsMzQzLjUwMDAwMSBMMTY1LjUsMzQzLjUwMDAwMSBaIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+PHBhdGggZD0iTTMwNC4yNSwxOTMuMzk2MjI2IEMzMDQuMjUsMTY1LjgwODIwMiAyODEuNDY1NDI0LDE0My41IDI1My40MjcwODMsMTQzLjUgQzIyNS4zODg3NDIsMTQzLjUgMjAyLjYwNDE2NywxNjUuODA4MjAyIDIwMi42MDQxNjcsMTkzLjM5NjIyNiBDMjAyLjYwNDE2NywyMjAuOTg0MjUgMjI1LjM4ODc0MiwyNDMuMjkyNDUzIDI1My40MjcwODMsMjQzLjI5MjQ1MyBDMjgxLjQ2NTQyNCwyNDMuMjkyNDUzIDMwNC4yNSwyMjAuOTg0MjUgMzA0LjI1LDE5My4zOTYyMjYgWiBNMjE1LjYwNDE2NywxOTMuMzk2MjI2IEMyMTUuNjA0MTY3LDE3My4wNTAxMDIgMjMyLjUwNzY4MywxNTYuNSAyNTMuNDI3MDgzLDE1Ni41IEMyNzQuMzQ2NDg0LDE1Ni41IDI5MS4yNSwxNzMuMDUwMTAyIDI5MS4yNSwxOTMuMzk2MjI2IEMyOTEuMjUsMjEzLjc0MjM1MSAyNzQuMzQ2NDg0LDIzMC4yOTI0NTMgMjUzLjQyNzA4MywyMzAuMjkyNDUzIEMyMzIuNTA3NjgzLDIzMC4yOTI0NTMgMjE1LjYwNDE2NywyMTMuNzQyMzUxIDIxNS42MDQxNjcsMTkzLjM5NjIyNiBaIiBza2V0Y2g6dHlwZT0iTVNTaGFwZUdyb3VwIj48L3BhdGg+PC9nPjwvZz48L3N2Zz4=",
|
||||||
|
component: AccountPage,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "security",
|
||||||
|
title: "Security",
|
||||||
|
icon: "data:image/svg+xml;base64,PHN2ZyBoZWlnaHQ9JzMwMHB4JyB3aWR0aD0nMzAwcHgnICBmaWxsPSIjMDAwMDAwIiB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjBweCIgeT0iMHB4IiB2aWV3Qm94PSIwIDAgNTEyIDUxMiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgNTEyIDUxMjsiIHhtbDpzcGFjZT0icHJlc2VydmUiPjxnPjxwYXRoIGQ9Ik00NDUuOCwzNy4zYy0xLjItMC43LTIuNy0wLjgtMy45LTAuMWMtNC4zLDIuMi0xMS42LDMuNC0yMS45LDMuNGMtMzMuNiwwLTkwLjgtMTIuNC0xMjguNy0yMC41Yy0xNC0zLTI2LjEtNS42LTM0LjEtNyAgIGMtMC40LTAuMS0wLjktMC4xLTEuNCwwYy03LjIsMS4yLTE4LjUsMy42LTMxLjcsNi4zQzE4NC4zLDI3LjYsMTI0LjIsNDAsOTAuNiw0MGMtMTEuNiwwLTE3LTEuNS0xOS41LTIuOCAgIGMtMS4yLTAuNi0yLjctMC42LTMuOSwwLjFzLTEuOSwyLTEuOSwzLjRjMCw3My4xLDMuOCwxNjguNCwzMy45LDI1Ny43YzE0LjMsNDIuOCwzMy41LDgwLjcsNTcuMSwxMTIuNyAgIGMyNi42LDM2LDU5LjcsNjUuNyw5OC4zLDg4LjNjMC42LDAuNCwxLjMsMC41LDIsMC41czEuNC0wLjIsMi0wLjVjMzguNi0yMi42LDcxLjYtNTIuMyw5OC4yLTg4LjNjMjMuNi0zMiw0Mi45LTY5LjksNTcuMS0xMTIuNyAgIGMyOS41LTg3LjYsMzMuNy0xNzkuNCwzMy45LTI1Ny43QzQ0Ny43LDM5LjQsNDQ3LDM4LjEsNDQ1LjgsMzcuM3ogTTQwNi4zLDI5NS45Yy0yOS4zLDg4LjEtNzkuNywxNTMuOC0xNDkuOCwxOTUuNCAgIEMxODYuNCw0NDkuNywxMzYsMzg0LDEwNi43LDI5NS45Qzc3LjgsMjEwLDczLjQsMTE4LjEsNzMuMiw0Ni40YzQuNSwxLjEsMTAuMiwxLjYsMTcuMywxLjZjMCwwLDAsMCwwLDAgICBjMzQuNSwwLDk1LjEtMTIuNiwxMzUuMi0yMC45YzEyLjctMi42LDIzLjYtNC45LDMwLjctNi4xYzcuOCwxLjQsMTkuNSwzLjksMzMuMSw2LjhDMzMwLDM2LjYsMzg1LjUsNDguNiw0MjAsNDguNiAgIGM4LjEsMCwxNC43LTAuNywxOS43LTJDNDM5LjMsMTIyLjksNDM0LjcsMjExLjUsNDA2LjMsMjk1Ljl6Ij48L3BhdGg+PHBhdGggZD0iTTI1Ni41LDIxNy44YzQ1LDAsODEuNi0zNi42LDgxLjYtODEuNmMwLTQ1LTM2LjYtODEuNi04MS42LTgxLjZjLTQ1LDAtODEuNiwzNi42LTgxLjYsODEuNiAgIEMxNzQuOSwxODEuMiwyMTEuNSwyMTcuOCwyNTYuNSwyMTcuOHogTTI1Ni41LDYyLjZjNDAuNiwwLDczLjYsMzMsNzMuNiw3My42YzAsNDAuNi0zMyw3My42LTczLjYsNzMuNmMtNDAuNiwwLTczLjYtMzMtNzMuNi03My42ICAgQzE4Mi45LDk1LjYsMjE1LjksNjIuNiwyNTYuNSw2Mi42eiI+PC9wYXRoPjxwYXRoIGQ9Ik0zMDkuMiwyMjguOUgyMDMuOGMtMjYuNSwwLTQ4LDIxLjUtNDgsNDh2NzljMCwyLjIsMS44LDQsNCw0aDE5My40YzIuMiwwLDQtMS44LDQtNHYtNzkgICBDMzU3LjIsMjUwLjQsMzM1LjYsMjI4LjksMzA5LjIsMjI4Ljl6IE0zNDkuMiwzNTEuOUgxNjMuOHYtNzVjMC0yMiwxNy45LTQwLDQwLTQwaDEwNS40YzIyLjEsMCw0MCwxNy45LDQwLDQwTDM0OS4yLDM1MS45ICAgTDM0OS4yLDM1MS45eiI+PC9wYXRoPjwvZz48L3N2Zz4=",
|
||||||
|
component: SecurityPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function getPage() {
|
||||||
|
let pageid = window.location.hash.slice(1);
|
||||||
|
return pages.find((e) => e.id === pageid) || pages[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
let page = getPage();
|
||||||
|
window.addEventListener("hashchange", () => {
|
||||||
|
page = getPage();
|
||||||
|
});
|
||||||
|
// $: title = pages.find(e => e.id === page).title;
|
||||||
|
|
||||||
|
const mq = window.matchMedia("(min-width: 45rem)");
|
||||||
|
let sidebar_button = !mq.matches;
|
||||||
|
mq.addEventListener("change", (ev) => {
|
||||||
|
sidebar_button = !ev.matches;
|
||||||
|
});
|
||||||
|
|
||||||
|
let sidebar_active = false;
|
||||||
|
|
||||||
|
function setPage(pageid) {
|
||||||
|
let pg = pages.find((e) => e.id === pageid);
|
||||||
|
if (!pg) {
|
||||||
|
throw new Error("Invalid Page " + pageid);
|
||||||
|
} else {
|
||||||
|
let url = new URL(window.location.href);
|
||||||
|
url.hash = pg.id;
|
||||||
|
window.history.pushState({}, pg.title, url);
|
||||||
|
page = getPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
sidebar_active = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let loading = true;
|
||||||
|
|
||||||
|
import NavigationBar from "./NavigationBar.svelte";
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class:loading class="root">
|
||||||
|
<div class="app_container">
|
||||||
|
<div class="header">
|
||||||
|
{#if sidebar_button}
|
||||||
|
<button on:click={() => (sidebar_active = !sidebar_active)}>
|
||||||
|
<svg
|
||||||
|
id="Layer_1"
|
||||||
|
style="enable-background:new 0 0 32 32;"
|
||||||
|
version="1.1"
|
||||||
|
viewBox="0 0 32 32"
|
||||||
|
width="32px"
|
||||||
|
xml:space="preserve"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M4,10h24c1.104,0,2-0.896,2-2s-0.896-2-2-2H4C2.896,6,2,6.896,2,8S2.896,10,4,10z
|
||||||
|
M28,14H4c-1.104,0-2,0.896-2,2
|
||||||
|
s0.896,2,2,2h24c1.104,0,2-0.896,2-2S29.104,14,28,14z
|
||||||
|
M28,22H4c-1.104,0-2,0.896-2,2s0.896,2,2,2h24c1.104,0,2-0.896,2-2
|
||||||
|
S29.104,22,28,22z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
{/if}
|
||||||
|
<h1>{page.title}</h1>
|
||||||
|
</div>
|
||||||
|
<div class="sidebar" class:sidebar-visible={sidebar_active}>
|
||||||
|
<NavigationBar open={setPage} {pages} active={page} />
|
||||||
|
</div>
|
||||||
|
<div class="content">
|
||||||
|
<svelte:component this={page.component} bind:loading />
|
||||||
|
</div>
|
||||||
|
<div class="footer" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{#if loading}
|
||||||
|
<div class="loader_container">
|
||||||
|
<div class="loader_box">
|
||||||
|
<div class="loader" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.loading {
|
||||||
|
background-color: rgba(0, 0, 0, 0.04);
|
||||||
|
filter: blur(10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--sidebar-width: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.root {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.app_container {
|
||||||
|
display: grid;
|
||||||
|
height: 100%;
|
||||||
|
grid-template-columns: auto 100%;
|
||||||
|
grid-template-rows: 60px auto 60px;
|
||||||
|
grid-template-areas:
|
||||||
|
"sidebar header"
|
||||||
|
"sidebar mc"
|
||||||
|
"sidebar footer";
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
grid-area: header;
|
||||||
|
background-color: var(--primary);
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header > h1 {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 36px;
|
||||||
|
color: white;
|
||||||
|
margin-left: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header > button {
|
||||||
|
height: 36px;
|
||||||
|
background-color: transparent;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header > button:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.151);
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
grid-area: sidebar;
|
||||||
|
transition: width 0.2s;
|
||||||
|
background-color: lightgrey;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-visible {
|
||||||
|
width: var(--sidebar-width);
|
||||||
|
transition: width 0.2s;
|
||||||
|
box-shadow: 10px 0px 10px 2px rgba(0, 0, 0, 0.52);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
grid-area: mc;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
grid-area: footer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 45rem) {
|
||||||
|
.app_container {
|
||||||
|
grid-template-columns: auto 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
width: var(--sidebar-width);
|
||||||
|
transition: all 0.2s;
|
||||||
|
box-shadow: 10px 0px 10px 2px rgba(0, 0, 0, 0.52);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
padding: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.loader_container {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
</style>
|
54
Frontend/src/pages/user/NavigationBar.svelte
Normal file
54
Frontend/src/pages/user/NavigationBar.svelte
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
<script>
|
||||||
|
export let open;
|
||||||
|
export let active;
|
||||||
|
export let pages = [];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#each pages as page}
|
||||||
|
<div
|
||||||
|
class={"item_container" + (page === active ? " active" : "")}
|
||||||
|
on:click={() => open(page.id)}
|
||||||
|
>
|
||||||
|
<div class="icon">
|
||||||
|
<img alt={page.title} src={page.icon} />
|
||||||
|
</div>
|
||||||
|
<h3 class="title">{page.title}</h3>
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
:root {
|
||||||
|
--rel-size: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item_container {
|
||||||
|
height: calc(var(--rel-size) * 5);
|
||||||
|
padding: var(--rel-size);
|
||||||
|
display: flex;
|
||||||
|
/* align-content: center; */
|
||||||
|
align-items: center;
|
||||||
|
/* justify-content: center; */
|
||||||
|
}
|
||||||
|
|
||||||
|
.active {
|
||||||
|
background: rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
/* float: left; */
|
||||||
|
width: calc(var(--rel-size) * 3);
|
||||||
|
height: calc(var(--rel-size) * 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon > img {
|
||||||
|
width: calc(var(--rel-size) * 3);
|
||||||
|
height: calc(var(--rel-size) * 3);
|
||||||
|
stroke-width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
/* margin: auto; */
|
||||||
|
margin-left: var(--rel-size);
|
||||||
|
/* height: 100%; */
|
||||||
|
}
|
||||||
|
</style>
|
192
Frontend/src/pages/user/Pages/Account.svelte
Normal file
192
Frontend/src/pages/user/Pages/Account.svelte
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
<script>
|
||||||
|
import Box from "./Box.svelte";
|
||||||
|
import BoxItem from "./BoxItem.svelte";
|
||||||
|
import NextIcon from "./NextIcon.svelte";
|
||||||
|
|
||||||
|
import request from "../../../helper/request.ts";
|
||||||
|
|
||||||
|
export let loading = false;
|
||||||
|
let account_error = undefined;
|
||||||
|
let contact_error = undefined;
|
||||||
|
|
||||||
|
const genderMap = new Map();
|
||||||
|
genderMap.set(0, "None");
|
||||||
|
genderMap.set(1, "Male");
|
||||||
|
genderMap.set(2, "Female");
|
||||||
|
genderMap.set(3, "Other");
|
||||||
|
|
||||||
|
let name = "";
|
||||||
|
let gender = 0;
|
||||||
|
$: genderHuman = genderMap.get(gender) || "ERROR";
|
||||||
|
let birthday = undefined;
|
||||||
|
|
||||||
|
async function saveName() {
|
||||||
|
//TODO: implement
|
||||||
|
await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveGender() {
|
||||||
|
//TODO: implement
|
||||||
|
await load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadProfile() {
|
||||||
|
try {
|
||||||
|
let { user } = await request(
|
||||||
|
"/api/user/account",
|
||||||
|
{},
|
||||||
|
"GET",
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
name = user.name;
|
||||||
|
// username = user.username;
|
||||||
|
gender = user.gender;
|
||||||
|
birthday = user.birthday
|
||||||
|
? new Date(user.birthday).toLocaleDateString()
|
||||||
|
: undefined;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
account_error = err.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let email = [];
|
||||||
|
let phone = [];
|
||||||
|
|
||||||
|
async function loadContact() {
|
||||||
|
try {
|
||||||
|
let { contact } = await request(
|
||||||
|
"/api/user/contact",
|
||||||
|
{},
|
||||||
|
"GET",
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
email = contact.mails.map((e) => e.mail);
|
||||||
|
phone = contact.phones.map((e) => e.phone);
|
||||||
|
contact_error = undefined;
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
contact_error = err.message;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function load() {
|
||||||
|
loading = true;
|
||||||
|
await Promise.all([loadProfile(), loadContact()]);
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
load();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn {
|
||||||
|
background-color: var(--primary);
|
||||||
|
margin: auto 0;
|
||||||
|
margin-left: 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container > *:first-child {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background-color: unset;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
color: unset;
|
||||||
|
font-size: unset;
|
||||||
|
border-bottom: 1px solid #757575;
|
||||||
|
/* Firefox */
|
||||||
|
-moz-appearance: none;
|
||||||
|
/* Safari and Chrome */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
select > option {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper::after {
|
||||||
|
content: ">";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 2rem;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1rem;
|
||||||
|
transform: rotate(90deg) scaleY(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.error {
|
||||||
|
color: var(--error);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<h1>Profile</h1>
|
||||||
|
{#if account_error}
|
||||||
|
<p class="error">{account_error}</p>
|
||||||
|
{/if}
|
||||||
|
<BoxItem name="Name" value={name}>
|
||||||
|
<div class="input-container">
|
||||||
|
<div class="floating group">
|
||||||
|
<input
|
||||||
|
id="name-inp"
|
||||||
|
type="text"
|
||||||
|
autocomplete="username"
|
||||||
|
bind:value={name} />
|
||||||
|
<span class="highlight" />
|
||||||
|
<span class="bar" />
|
||||||
|
<label for="name-inp">Name</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn" on:click={saveName}>Save</button>
|
||||||
|
</div>
|
||||||
|
</BoxItem>
|
||||||
|
<BoxItem name="Gender" value={genderHuman}>
|
||||||
|
<div class="input-container">
|
||||||
|
<div class="select-wrapper">
|
||||||
|
<select bind:value={gender}>
|
||||||
|
<option value={1}>Male</option>
|
||||||
|
<option value={2}>Female</option>
|
||||||
|
<option value={3}>Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn" on:click={saveGender}>Save</button>
|
||||||
|
</div>
|
||||||
|
</BoxItem>
|
||||||
|
<BoxItem name="Birthday" value={birthday} />
|
||||||
|
<BoxItem name="Password" value="******" />
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<h1>Contact</h1>
|
||||||
|
{#if contact_error}
|
||||||
|
<p class="error">{contact_error}</p>
|
||||||
|
{/if}
|
||||||
|
<BoxItem name="E-Mail" value={email} noOpen={true} />
|
||||||
|
<BoxItem name="Phone" value={phone} noOpen={true} />
|
||||||
|
</Box>
|
36
Frontend/src/pages/user/Pages/Box.svelte
Normal file
36
Frontend/src/pages/user/Pages/Box.svelte
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<style>
|
||||||
|
.box {
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 8px 12px rgba(0, 0, 0, 0.30), 0 5px 4px rgba(0, 0, 0, 0.22);
|
||||||
|
padding: 2rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box> :global(h1) {
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
color: #444444;
|
||||||
|
font-size: 1.3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box> :global(div) {
|
||||||
|
padding: 16px;
|
||||||
|
border-top: 1px solid var(--border-color);
|
||||||
|
word-wrap: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.box> :global(div):first-of-type {
|
||||||
|
border-top: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 45rem) {
|
||||||
|
.box {
|
||||||
|
margin-bottom: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
94
Frontend/src/pages/user/Pages/BoxItem.svelte
Normal file
94
Frontend/src/pages/user/Pages/BoxItem.svelte
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
<script>
|
||||||
|
import { slide } from "svelte/transition";
|
||||||
|
import NextIcon from "./NextIcon.svelte";
|
||||||
|
export let name = "";
|
||||||
|
export let value = "";
|
||||||
|
export let noOpen = false;
|
||||||
|
export let open = false;
|
||||||
|
export let highlight = false;
|
||||||
|
|
||||||
|
function toggleOpen(ev) {}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.root:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.values {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
max-width: calc(100% - var(--default-font-size) - 16px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.values > div:first-child {
|
||||||
|
transform-origin: left;
|
||||||
|
transform: scale(0.95);
|
||||||
|
margin-right: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.values > div:nth-child(2) {
|
||||||
|
color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
:global(svg) {
|
||||||
|
margin: auto 8px auto 8px;
|
||||||
|
height: var(--default-font-size);
|
||||||
|
min-width: var(--default-font-size);
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0.1px;
|
||||||
|
margin-top: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 45rem) {
|
||||||
|
.values {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.values > div:first-child {
|
||||||
|
transform: unset;
|
||||||
|
flex-basis: 120px;
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight-element {
|
||||||
|
background-color: #7bff003b;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="root" class:highlight-element={highlight}>
|
||||||
|
<div class="container" on:click={() => (open = !open)}>
|
||||||
|
<div class="values">
|
||||||
|
<div>{name}</div>
|
||||||
|
<div>
|
||||||
|
{#if Array.isArray(value)}
|
||||||
|
{#each value as v, i}
|
||||||
|
{v}
|
||||||
|
{#if i < value.length - 1}
|
||||||
|
<br />
|
||||||
|
{/if}
|
||||||
|
{/each}
|
||||||
|
{:else}{value}{/if}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#if !noOpen}
|
||||||
|
<NextIcon rotation={open ? -90 : 90} />
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
{#if open && !noOpen}
|
||||||
|
<div class="body" transition:slide>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
|
</div>
|
13
Frontend/src/pages/user/Pages/NextIcon.svelte
Normal file
13
Frontend/src/pages/user/Pages/NextIcon.svelte
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<script>
|
||||||
|
export let rotation;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svg style={`enable-background:new 0 0 35.414 35.414; transform: rotate(${rotation}deg); transition: all .4s;`}
|
||||||
|
version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||||
|
y="0px" viewBox="0 0 35.414 35.414" xml:space="preserve">
|
||||||
|
<g>
|
||||||
|
<g>
|
||||||
|
<polygon points="27.051,17 9.905,0 8.417,1.414 24.674,17.707 8.363,34 9.914,35.414 27.051,18.414 " />
|
||||||
|
</g>
|
||||||
|
</g>
|
||||||
|
</svg>
|
188
Frontend/src/pages/user/Pages/Security.svelte
Normal file
188
Frontend/src/pages/user/Pages/Security.svelte
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<script context="module">
|
||||||
|
const TFATypes = new Map();
|
||||||
|
TFATypes.set(0, "Authenticator");
|
||||||
|
TFATypes.set(1, "Backup Codes");
|
||||||
|
TFATypes.set(2, "YubiKey");
|
||||||
|
TFATypes.set(3, "Push Notification");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Box from "./Box.svelte";
|
||||||
|
import BoxItem from "./BoxItem.svelte";
|
||||||
|
import NextIcon from "./NextIcon.svelte";
|
||||||
|
import request from "../../../helper/request.ts";
|
||||||
|
|
||||||
|
export let loading = false;
|
||||||
|
|
||||||
|
let twofactor = [];
|
||||||
|
|
||||||
|
async function deleteTFA(id) {
|
||||||
|
let res = await request(
|
||||||
|
"/api/user/twofactor/" + id,
|
||||||
|
undefined,
|
||||||
|
"DELETE",
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
loadTwoFactor();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadTwoFactor() {
|
||||||
|
let res = await request(
|
||||||
|
"/api/user/twofactor",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
twofactor = res.methods;
|
||||||
|
}
|
||||||
|
|
||||||
|
let token = [];
|
||||||
|
|
||||||
|
async function revoke(id) {
|
||||||
|
let res = await request(
|
||||||
|
"/api/user/token/" + id,
|
||||||
|
undefined,
|
||||||
|
"DELETE",
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
loadToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadToken() {
|
||||||
|
loading = true;
|
||||||
|
let res = await request(
|
||||||
|
"/api/user/token",
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
undefined,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
token = res.token;
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
loadToken();
|
||||||
|
loadTwoFactor();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.btn {
|
||||||
|
background-color: var(--primary);
|
||||||
|
margin: auto 0;
|
||||||
|
margin-left: 1rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
padding: 0 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.floating {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-container > *:first-child {
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
background-color: unset;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0;
|
||||||
|
color: unset;
|
||||||
|
font-size: unset;
|
||||||
|
border-bottom: 1px solid #757575;
|
||||||
|
/* Firefox */
|
||||||
|
-moz-appearance: none;
|
||||||
|
/* Safari and Chrome */
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
select > option {
|
||||||
|
background-color: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.select-wrapper::after {
|
||||||
|
content: ">";
|
||||||
|
display: block;
|
||||||
|
position: absolute;
|
||||||
|
right: 2rem;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
width: 1rem;
|
||||||
|
transform: rotate(90deg) scaleY(2);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<h1>Two Factor</h1>
|
||||||
|
<BoxItem name="Add new" open={false} />
|
||||||
|
{#each twofactor as t}
|
||||||
|
<BoxItem name={TFATypes.get(t.type)} value={t.name} highlight={t.isthis}>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
style="background: var(--error)"
|
||||||
|
on:click={() => deleteTFA(t.id)}>
|
||||||
|
Delete
|
||||||
|
</button>
|
||||||
|
</BoxItem>
|
||||||
|
{/each}
|
||||||
|
<!-- <BoxItem name="Name" value={name} open={false}>
|
||||||
|
<div class="input-container">
|
||||||
|
<div class="floating group">
|
||||||
|
<input type="text" autocomplete="username" bind:value={name}>
|
||||||
|
<span class="highlight"></span>
|
||||||
|
<span class="bar"></span>
|
||||||
|
<label>Name</label>
|
||||||
|
</div>
|
||||||
|
<button class="btn" on:click={saveName}>Save</button>
|
||||||
|
</div>
|
||||||
|
</BoxItem>
|
||||||
|
<BoxItem name="Gender" value={gender} open={true}>
|
||||||
|
<div class="input-container">
|
||||||
|
<div class="select-wrapper">
|
||||||
|
<select>
|
||||||
|
<option value="1">Male</option>
|
||||||
|
<option value="2">Female</option>
|
||||||
|
<option value="3">Other</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<button class="btn" on:click={saveName}>Save</button>
|
||||||
|
</div>
|
||||||
|
</BoxItem>
|
||||||
|
<BoxItem name="Birthday" value={birthday} />
|
||||||
|
<BoxItem name="Password" value="******" /> -->
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
<Box>
|
||||||
|
<h1>Anmeldungen</h1>
|
||||||
|
|
||||||
|
{#each token as t}
|
||||||
|
<BoxItem name={t.browser} value={t.ip} highlight={t.isthis}>
|
||||||
|
<button
|
||||||
|
class="btn"
|
||||||
|
style="background: var(--error)"
|
||||||
|
on:click={() => revoke(t.id)}>
|
||||||
|
Revoke
|
||||||
|
</button>
|
||||||
|
</BoxItem>
|
||||||
|
{:else}<span>No Tokens</span>{/each}
|
||||||
|
|
||||||
|
<!-- <BoxItem name="E-Mail" value={email} />
|
||||||
|
<BoxItem name="Phone" value={phone} /> -->
|
||||||
|
</Box>
|
6
Frontend/src/pages/user/main.ts
Normal file
6
Frontend/src/pages/user/main.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import "../../components/theme";
|
||||||
|
import App from "./App.svelte";
|
||||||
|
|
||||||
|
new App({
|
||||||
|
target: document.body,
|
||||||
|
});
|
5
Frontend/svelte.config.js
Normal file
5
Frontend/svelte.config.js
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
const preprocess = require("svelte-preprocess");
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
preprocess: preprocess({}),
|
||||||
|
};
|
5
Frontend/tsconfig.json
Normal file
5
Frontend/tsconfig.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/svelte/tsconfig.json",
|
||||||
|
|
||||||
|
"include": ["src/**/*", "src/node_modules"]
|
||||||
|
}
|
@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "@hibas123/openauth-views-v1",
|
"name": "@hibas123/openauth-views-v1",
|
||||||
"version": "1.0.0",
|
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@hibas123/openauth",
|
"name": "@hibas123/openauth",
|
||||||
|
"version": "1.2.0",
|
||||||
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
"author": "Fabian Stamm <dev@fabianstamm.de>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
Loading…
Reference in New Issue
Block a user