From 922ed1e8134958d90129afa144e74981e67063b9 Mon Sep 17 00:00:00 2001 From: Fabian Stamm Date: Sun, 9 Apr 2023 18:20:43 +0200 Subject: [PATCH] Start implementing a new user page for account and security settings --- Backend/package.json | 1 + Backend/src/api/index.ts | 21 + Backend/src/api/jrpc/account_service.ts | 49 + Backend/src/api/jrpc/index.ts | 45 + Backend/src/api/jrpc/security_service.ts | 71 ++ Backend/src/api/oauth/profile.ts | 2 + .../src/api/user/twofactor/backup/index.ts | 4 +- Backend/src/api/user/twofactor/otc/index.ts | 6 +- .../src/api/user/twofactor/yubikey/index.ts | 8 +- Backend/src/models/twofactor.ts | 10 +- Backend/src/testdata.ts | 1 + Backend/src/views/views.ts | 8 +- Frontend/package.json | 9 + Frontend/postcss.config.js | 6 +- Frontend/rollup.config.mjs | 29 +- Frontend/src/helper/api.ts | 23 + Frontend/src/helper/request.ts | 11 +- Frontend/src/main.css | 68 ++ Frontend/src/pages/home/App.svelte | 30 +- Frontend/src/pages/user/App.svelte | 216 +--- Frontend/src/pages/user/Loading.svelte | 19 + .../src/pages/user/Pages/PersonalInfo.svelte | 197 ++++ Frontend/src/pages/user/Pages/Security.svelte | 311 +++--- Frontend/src/pages/user/Sidebar.svelte | 34 + Frontend/src/pages/user/main.ts | 2 +- Frontend/src/pages/user/nav.ts | 25 + Frontend/src/pages/user_old/App.svelte | 207 ++++ .../{user => user_old}/NavigationBar.svelte | 0 .../{user => user_old}/Pages/Account.svelte | 0 .../pages/{user => user_old}/Pages/Box.svelte | 0 .../{user => user_old}/Pages/BoxItem.svelte | 0 .../{user => user_old}/Pages/NextIcon.svelte | 0 .../src/pages/user_old/Pages/Security.svelte | 188 ++++ Frontend/src/pages/user_old/main.ts | 6 + ...ial-icons-outlined-v109-latin-regular.woff | Bin 0 -> 182028 bytes ...al-icons-outlined-v109-latin-regular.woff2 | Bin 0 -> 155276 bytes .../material-icons-v140-latin-regular.woff | Bin 0 -> 164912 bytes .../material-icons-v140-latin-regular.woff2 | Bin 0 -> 128352 bytes Frontend/tailwind.config.js | 15 + InternalAPI/api.jrpc | 77 ++ _API/.gitignore | 3 + _API/package.json | 19 + _API/tsconfig-esm.json | 21 + _API/tsconfig.json | 21 + package.json | 9 +- yarn.lock | 978 +++++++++++++++++- 46 files changed, 2307 insertions(+), 443 deletions(-) create mode 100644 Backend/src/api/jrpc/account_service.ts create mode 100644 Backend/src/api/jrpc/index.ts create mode 100644 Backend/src/api/jrpc/security_service.ts create mode 100644 Frontend/src/helper/api.ts create mode 100644 Frontend/src/main.css create mode 100644 Frontend/src/pages/user/Loading.svelte create mode 100644 Frontend/src/pages/user/Pages/PersonalInfo.svelte create mode 100644 Frontend/src/pages/user/Sidebar.svelte create mode 100644 Frontend/src/pages/user/nav.ts create mode 100644 Frontend/src/pages/user_old/App.svelte rename Frontend/src/pages/{user => user_old}/NavigationBar.svelte (100%) rename Frontend/src/pages/{user => user_old}/Pages/Account.svelte (100%) rename Frontend/src/pages/{user => user_old}/Pages/Box.svelte (100%) rename Frontend/src/pages/{user => user_old}/Pages/BoxItem.svelte (100%) rename Frontend/src/pages/{user => user_old}/Pages/NextIcon.svelte (100%) create mode 100644 Frontend/src/pages/user_old/Pages/Security.svelte create mode 100644 Frontend/src/pages/user_old/main.ts create mode 100644 Frontend/static/material-icons-outlined-v109-latin-regular.woff create mode 100644 Frontend/static/material-icons-outlined-v109-latin-regular.woff2 create mode 100644 Frontend/static/material-icons-v140-latin-regular.woff create mode 100644 Frontend/static/material-icons-v140-latin-regular.woff2 create mode 100644 Frontend/tailwind.config.js create mode 100644 InternalAPI/api.jrpc create mode 100644 _API/.gitignore create mode 100644 _API/package.json create mode 100644 _API/tsconfig-esm.json create mode 100644 _API/tsconfig.json diff --git a/Backend/package.json b/Backend/package.json index 4774d78..d9f5bff 100644 --- a/Backend/package.json +++ b/Backend/package.json @@ -45,6 +45,7 @@ "@hibas123/config": "^1.1.2", "@hibas123/nodelogging": "^3.1.3", "@hibas123/nodeloggingserver_client": "^1.1.2", + "@hibas123/openauth-internalapi": "workspace:^", "@hibas123/openauth-views-v1": "workspace:^", "@hibas123/safe_mongo": "^1.7.1", "body-parser": "^1.20.2", diff --git a/Backend/src/api/index.ts b/Backend/src/api/index.ts index d648ea3..09db7a4 100644 --- a/Backend/src/api/index.ts +++ b/Backend/src/api/index.ts @@ -7,6 +7,7 @@ import ClientRouter from "./client"; import * as cors from "cors"; import OAuthRoute from "./oauth"; import config from "../config"; +import JRPCEndpoint from "./jrpc"; const ApiRouter: express.IRouter = express.Router(); ApiRouter.use("/admin", AdminRoute); @@ -17,6 +18,26 @@ ApiRouter.use("/oauth", OAuthRoute); ApiRouter.use("/client", ClientRouter); +/** + * @api {post} /jrpc + * @apiName InternalJRPCEndpoint + * + * @apiGroup user + * @apiPermission none + * + * @apiErrorExample {Object} Error-Response: + { + error: [ + { + message: "Some Error", + field: "username" + } + ], + status: 400 + } + */ +ApiRouter.post("/jrpc", JRPCEndpoint); + // Legacy reasons (deprecated) ApiRouter.use("/", ClientRouter); diff --git a/Backend/src/api/jrpc/account_service.ts b/Backend/src/api/jrpc/account_service.ts new file mode 100644 index 0000000..f109456 --- /dev/null +++ b/Backend/src/api/jrpc/account_service.ts @@ -0,0 +1,49 @@ +import { Account, ContactInfo, Gender, Server, UserRegisterInfo } from "@hibas123/openauth-internalapi"; +import type { SessionContext } from "./index"; +import Mail from "../../models/mail"; +import User from "../../models/user"; + +export default class AccountService extends Server.AccountService { + + Register(regcode: string, info: UserRegisterInfo, ctx: SessionContext): Promise { + throw new Error("Method not implemented."); + } + + async GetProfile(ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + + return { + id: ctx.user.uid, + username: ctx.user.username, + name: ctx.user.name, + birthday: ctx.user.birthday.valueOf(), + gender: ctx.user.gender as number as Gender, + } + } + + async UpdateProfile(info: Account, ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + ctx.user.name = info.name; + ctx.user.birthday = new Date(info.birthday); + ctx.user.gender = info.gender as number; + + await User.save(ctx.user); + } + + async GetContactInfos(ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + let mails = await Promise.all( + ctx.user.mails.map((mail) => Mail.findById(mail)) + ); + + let contact = { + mail: mails.filter((e) => !!e), + phone: ctx.user.phones, + }; + + return contact; + } +} diff --git a/Backend/src/api/jrpc/index.ts b/Backend/src/api/jrpc/index.ts new file mode 100644 index 0000000..8e286cc --- /dev/null +++ b/Backend/src/api/jrpc/index.ts @@ -0,0 +1,45 @@ +import { Request, Response } from "express"; +import Stacker from "../middlewares/stacker"; +import { GetUserMiddleware } from "../middlewares/user"; +import { IUser } from "../../models/user"; +import { Server } from "@hibas123/openauth-internalapi"; +import AccountService from "./account_service"; +import SecurityService from "./security_service"; +import { ILoginToken } from "../../models/login_token"; + +export interface SessionContext { + user: IUser, + request: Request, + isAdmin: boolean, + special: boolean, + token: { + login: ILoginToken, + special?: ILoginToken, + } +} + +const provider = new Server.ServiceProvider(); +provider.addService(new AccountService()); +provider.addService(new SecurityService()); + +const JRPCEndpoint = Stacker( + GetUserMiddleware(true, true), + async (req: Request, res: Response) => { + const session = provider.getSession((data) => { + res.json(data); + }, { + user: req.user, + request: req, + isAdmin: req.isAdmin, + special: req.special, + token: { + login: req.token.login, + special: req.token.special, + } + }); + + session.onMessage(req.body); + } +); + +export default JRPCEndpoint; diff --git a/Backend/src/api/jrpc/security_service.ts b/Backend/src/api/jrpc/security_service.ts new file mode 100644 index 0000000..3360ba8 --- /dev/null +++ b/Backend/src/api/jrpc/security_service.ts @@ -0,0 +1,71 @@ +import { Server, Token, TwoFactor, UserRegisterInfo } from "@hibas123/openauth-internalapi"; +import type { SessionContext } from "./index"; +import LoginToken, { CheckToken } from "../../models/login_token"; +import TwoFactorModel from "../../models/twofactor"; +import moment = require("moment"); + +export default class SecurityService extends Server.SecurityService { + async GetTokens(ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + let raw_token = await LoginToken.find({ + user: ctx.user._id, + valid: true, + }); + let token = await Promise.all( + raw_token + .map>(async (token) => { + await CheckToken(token); + return { + id: token._id.toString(), + special: token.special, + ip: token.ip, + browser: token.browser, + isthis: token._id.equals( + token.special ? ctx.token.special._id : ctx.token.login._id + ), + }; + }) + .filter((t) => t !== undefined) + ); + + return token + } + async RevokeToken(id: string, ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + let token = await LoginToken.findById(id); + if (!token || !token.user.equals(ctx.user._id)) + throw new Error("Invalid ID"); + token.valid = false; + await LoginToken.save(token); + } + + async GetTwofactorOptions(ctx: SessionContext): Promise { + if (!ctx.user) throw new Error("Not logged in"); + + + let twofactor = await TwoFactorModel.find({ user: ctx.user._id, valid: true }); + let expired = twofactor.filter((e) => + e.expires ? moment().isAfter(moment(e.expires)) : false + ); + await Promise.all( + expired.map((e) => { + e.valid = false; + return TwoFactorModel.save(e); + }) + ); + + twofactor = twofactor.filter((e) => e.valid); + let tfa = twofactor.map((e) => { + return { + id: e._id.toString(), + name: e.name, + tfatype: e.type as number, + expires: e.expires?.valueOf() + }; + }); + + return tfa; + } +} diff --git a/Backend/src/api/oauth/profile.ts b/Backend/src/api/oauth/profile.ts index 3e0ea36..6849b7b 100644 --- a/Backend/src/api/oauth/profile.ts +++ b/Backend/src/api/oauth/profile.ts @@ -18,6 +18,8 @@ export default Stacker(GetClientApiAuthMiddleware(), async (req: Request, res) = email: mail.mail, username: req.user.username, displayName: req.user.name, + "display-name": req.user.name, displayNameClaim: req.user.name, + name: req.user.name, }); }) diff --git a/Backend/src/api/user/twofactor/backup/index.ts b/Backend/src/api/user/twofactor/backup/index.ts index 98f546e..88a420b 100644 --- a/Backend/src/api/user/twofactor/backup/index.ts +++ b/Backend/src/api/user/twofactor/backup/index.ts @@ -33,7 +33,7 @@ BackupCodeRoute.post( console.log(codes); let twofactor = TwoFactor.new({ user: req.user._id, - type: TwoFATypes.OTC, + type: TwoFATypes.TOTP, valid: true, data: codes, name: "", @@ -60,7 +60,7 @@ BackupCodeRoute.put( !twofactor || !twofactor.valid || !twofactor.user.equals(req.user._id) || - twofactor.type !== TwoFATypes.OTC + twofactor.type !== TwoFATypes.TOTP ) { throw new RequestError( "Invalid Method!", diff --git a/Backend/src/api/user/twofactor/otc/index.ts b/Backend/src/api/user/twofactor/otc/index.ts index f0dd4d1..8eeb900 100644 --- a/Backend/src/api/user/twofactor/otc/index.ts +++ b/Backend/src/api/user/twofactor/otc/index.ts @@ -28,7 +28,7 @@ OTCRoute.post( }); let twofactor = TwoFactor.new({ user: req.user._id, - type: TwoFATypes.OTC, + type: TwoFATypes.TOTP, valid: false, data: secret.base32, }); @@ -49,7 +49,7 @@ OTCRoute.post( if ( !twofactor || !twofactor.user.equals(req.user._id) || - twofactor.type !== TwoFATypes.OTC || + twofactor.type !== TwoFATypes.TOTP || !twofactor.data || twofactor.valid ) { @@ -96,7 +96,7 @@ OTCRoute.put( !twofactor || !twofactor.valid || !twofactor.user.equals(req.user._id) || - twofactor.type !== TwoFATypes.OTC + twofactor.type !== TwoFATypes.TOTP ) { throw new RequestError( "Invalid Method!", diff --git a/Backend/src/api/user/twofactor/yubikey/index.ts b/Backend/src/api/user/twofactor/yubikey/index.ts index 6826207..472f3a4 100644 --- a/Backend/src/api/user/twofactor/yubikey/index.ts +++ b/Backend/src/api/user/twofactor/yubikey/index.ts @@ -27,7 +27,7 @@ U2FRoute.post( let twofactor = TwoFactor.new({ user: req.user._id, - type: TwoFATypes.U2F, + type: TwoFATypes.WEBAUTHN, valid: false, data: { registration: registrationRequest, @@ -49,7 +49,7 @@ U2FRoute.post( if ( !twofactor || !twofactor.user.equals(req.user._id) || - twofactor.type !== TwoFATypes.U2F || + twofactor.type !== TwoFATypes.WEBAUTHN || !twofactor.data.registration || twofactor.valid ) { @@ -95,7 +95,7 @@ U2FRoute.get( let { login, special } = req.token; let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, - type: TwoFATypes.U2F, + type: TwoFATypes.WEBAUTHN, valid: true, }); @@ -142,7 +142,7 @@ U2FRoute.put( let { login, special } = req.token; let twofactor: IYubiKey = await TwoFactor.findOne({ user: req.user._id, - type: TwoFATypes.U2F, + type: TwoFATypes.WEBAUTHN, valid: true, }); diff --git a/Backend/src/models/twofactor.ts b/Backend/src/models/twofactor.ts index b225df2..2e04164 100644 --- a/Backend/src/models/twofactor.ts +++ b/Backend/src/models/twofactor.ts @@ -3,16 +3,16 @@ import { ModelDataBase } from "@hibas123/safe_mongo/lib/model"; import { ObjectID } from "bson"; export enum TFATypes { - OTC, + TOTP, BACKUP_CODE, - U2F, + WEBAUTHN, APP_ALLOW, } export const TFANames = new Map(); -TFANames.set(TFATypes.OTC, "Authenticator"); +TFANames.set(TFATypes.TOTP, "Authenticator"); TFANames.set(TFATypes.BACKUP_CODE, "Backup Codes"); -TFANames.set(TFATypes.U2F, "Security Key (U2F)"); +TFANames.set(TFATypes.WEBAUTHN, "Security Key (WebAuthn)"); TFANames.set(TFATypes.APP_ALLOW, "App Push"); export interface ITwoFactor extends ModelDataBase { @@ -53,7 +53,7 @@ const TwoFactor = DB.addModel({ name: "twofactor", versions: [ { - migration: (e) => {}, + migration: (e) => { }, schema: { user: { type: ObjectID }, valid: { type: Boolean }, diff --git a/Backend/src/testdata.ts b/Backend/src/testdata.ts index 2744316..2e8e364 100644 --- a/Backend/src/testdata.ts +++ b/Backend/src/testdata.ts @@ -96,6 +96,7 @@ export default async function TestData() { if (!t) { t = TwoFactor.new({ user: u._id, + name: "Test OTP", type: 0, valid: true, data: "IIRW2P2UJRDDO2LDIRYW4LSREZLWMOKDNBJES2LLHRREK3R6KZJQ", diff --git a/Backend/src/views/views.ts b/Backend/src/views/views.ts index b3ed638..9f56831 100644 --- a/Backend/src/views/views.ts +++ b/Backend/src/views/views.ts @@ -49,7 +49,13 @@ ViewRouter.use( ViewRouter.use( "/user", addCache, - ServeStatic(path.join(viewsv2_location, "user"), { cacheControl: false }) + ServeStatic(path.join(viewsv2_location, "user"), { cacheControl: false, }) +); + +ViewRouter.use( + "/static", + addCache, + ServeStatic(path.join(viewsv2_location, "../static"), { cacheControl: false, }) ); ViewRouter.get("/code", (req, res) => { diff --git a/Frontend/package.json b/Frontend/package.json index 23fdfb4..8f9cdc8 100644 --- a/Frontend/package.json +++ b/Frontend/package.json @@ -2,12 +2,18 @@ "name": "@hibas123/openauth-views-v2", "main": "index.js", "devDependencies": { + "@popperjs/core": "^2.11.7", "@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", + "autoprefixer": "^10.4.14", + "classnames": "^2.3.2", + "cssnano": "^6.0.0", "esbuild": "^0.17.15", + "flowbite": "^1.6.4", + "flowbite-svelte": "^0.34.7", "postcss": "^8.4.21", "postcss-import": "^15.1.0", "postcss-url": "^10.1.3", @@ -20,6 +26,7 @@ "rollup-plugin-visualizer": "^5.9.0", "svelte": "^3.58.0", "svelte-preprocess": "^5.0.3", + "tailwindcss": "^3.3.1", "typescript": "^5.0.3" }, "scripts": { @@ -28,8 +35,10 @@ "dev": "rollup -c rollup.config.mjs -w" }, "dependencies": { + "@hibas123/openauth-internalapi": "workspace:^", "@hibas123/theme": "^2.0.6", "@hibas123/utils": "^2.2.18", + "@rollup/plugin-commonjs": "^24.0.1", "cleave.js": "^1.6.0", "what-the-pack": "^2.0.3" } diff --git a/Frontend/postcss.config.js b/Frontend/postcss.config.js index f913c37..697c61e 100644 --- a/Frontend/postcss.config.js +++ b/Frontend/postcss.config.js @@ -1,3 +1,7 @@ module.exports = { - plugins: [], + plugins: { + tailwindcss: {}, + autoprefixer: {}, + cssnano: {}, + }, }; diff --git a/Frontend/rollup.config.mjs b/Frontend/rollup.config.mjs index 40b2aeb..5098c3d 100644 --- a/Frontend/rollup.config.mjs +++ b/Frontend/rollup.config.mjs @@ -8,6 +8,7 @@ import { visualizer } from "rollup-plugin-visualizer"; import postcss from "rollup-plugin-postcss"; import livereload from "rollup-plugin-livereload"; import sveltePreprocess from "svelte-preprocess"; +import commonjs from "@rollup/plugin-commonjs"; const VIEWS = ["home", "login", "popup", "user"]; @@ -66,30 +67,20 @@ const htmlTemplate = ({ attributes, meta, files, publicPath, title }) => { 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, - }), - ], - }, + { + file: `build/${view}/bundle.min.js`, + format: "es", + sourcemap: true, + name: view, + }, ], plugins: [ svelte({ emitCss: true, preprocess: sveltePreprocess({}), }), - esbuild({ sourceMap: dev }), + commonjs(), + esbuild({ sourceMap: dev, minify: true }), html({ title: view, attributes: { @@ -105,8 +96,8 @@ export default VIEWS.map((view) => ({ }), resolve({ browser: true, - dedupe: ["svelte"], exportConditions: ["svelte"], + extensions: [".svelte"], }), image(), sizes(), diff --git a/Frontend/src/helper/api.ts b/Frontend/src/helper/api.ts new file mode 100644 index 0000000..9419778 --- /dev/null +++ b/Frontend/src/helper/api.ts @@ -0,0 +1,23 @@ +import { Client } from "@hibas123/openauth-internalapi"; +import request, { RequestError } from "./request"; + +const provider = new Client.ServiceProvider((data) => { + request("/api/jrpc", {}, "POST", data, true, true).then(result => { + provider.onPacket(result); + }).catch(err => { + if (err instanceof RequestError) { + let data = err.response; + if (data.error && Array.isArray(data.error)) { + data.error = data.error[0]; + } + provider.onPacket(data); + } + }); +}); + +const InternalAPI = { + Account: new Client.AccountService(provider), + Security: new Client.SecurityService(provider), +} + +export default InternalAPI; diff --git a/Frontend/src/helper/request.ts b/Frontend/src/helper/request.ts index 1d620b6..0579637 100644 --- a/Frontend/src/helper/request.ts +++ b/Frontend/src/helper/request.ts @@ -2,6 +2,15 @@ import { getCookie } from "./cookie"; const baseURL = ""; +export class RequestError extends Error { + response: any; + constructor(message: string, response: any) { + super(message); + this.name = "RequestError"; + this.response = response; + } +} + export default async function request( endpoint: string, parameters: { [key: string]: string } = {}, @@ -46,7 +55,7 @@ export default async function request( ); window.location.href = `/login?state=${state}&base64=true`; } - return Promise.reject(new Error(data.error)); + return Promise.reject(new RequestError(data.error, data)); } return data; }); diff --git a/Frontend/src/main.css b/Frontend/src/main.css new file mode 100644 index 0000000..50d5218 --- /dev/null +++ b/Frontend/src/main.css @@ -0,0 +1,68 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +/* material-icons-regular - latin */ +@font-face { + font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ + font-family: "Material Icons"; + font-style: normal; + font-weight: 400; + src: url("/static/material-icons-v140-latin-regular.woff2") format("woff2"), + /* Chrome 36+, Opera 23+, Firefox 39+ */ + url("/static/material-icons-v140-latin-regular.woff") format("woff"); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +/* material-icons-outlined-regular - latin */ +@font-face { + font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */ + font-family: "Material Icons Outlined"; + font-style: normal; + font-weight: 400; + src: url("/static/material-icons-outlined-v109-latin-regular.woff2") + format("woff2"), + /* Chrome 36+, Opera 23+, Firefox 39+ */ + url("/static/material-icons-outlined-v109-latin-regular.woff") + format("woff"); /* Chrome 5+, Firefox 3.6+, IE 9+, Safari 5.1+ */ +} + +.material-icons { + font-family: "Material Icons"; + font-weight: normal; + font-style: normal; + font-size: 24px; /* Preferred icon size */ + display: inline-block; + line-height: 1; + text-transform: none; + letter-spacing: normal; + word-wrap: normal; + white-space: nowrap; + direction: ltr; + + /* Support for all WebKit browsers. */ + -webkit-font-smoothing: antialiased; + /* Support for Safari and Chrome. */ + text-rendering: optimizeLegibility; + + /* Support for Firefox. */ + -moz-osx-font-smoothing: grayscale; + + /* Support for IE. */ + font-feature-settings: "liga"; +} + +.material-icons-outlined { + font-family: "Material Icons Outlined"; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: "liga"; + -webkit-font-smoothing: antialiased; +} diff --git a/Frontend/src/pages/home/App.svelte b/Frontend/src/pages/home/App.svelte index 0e90ae8..e753907 100644 --- a/Frontend/src/pages/home/App.svelte +++ b/Frontend/src/pages/home/App.svelte @@ -1,18 +1,3 @@ - -

Home Page

@@ -42,3 +27,18 @@
+ + diff --git a/Frontend/src/pages/user/App.svelte b/Frontend/src/pages/user/App.svelte index 835f487..9e69188 100644 --- a/Frontend/src/pages/user/App.svelte +++ b/Frontend/src/pages/user/App.svelte @@ -1,207 +1,25 @@ - -
-
-
- {#if sidebar_button} - - {/if} -

{page.title}

-
- -
- -
-