Login System + API
This commit is contained in:
@@ -1,13 +1,19 @@
|
|||||||
import { defineConfig } from "astro/config";
|
import { defineConfig } from "astro/config";
|
||||||
|
|
||||||
// https://astro.build/config
|
|
||||||
import svelte from "@astrojs/svelte";
|
import svelte from "@astrojs/svelte";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
import tailwind from "@astrojs/tailwind";
|
import tailwind from "@astrojs/tailwind";
|
||||||
|
|
||||||
|
// https://astro.build/config
|
||||||
|
import node from "@astrojs/node";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
integrations: [svelte(), tailwind()],
|
integrations: [svelte(), tailwind()],
|
||||||
outDir: "./dist"
|
outDir: "./dist",
|
||||||
|
output: "server",
|
||||||
|
adapter: node({
|
||||||
|
mode: "standalone"
|
||||||
|
})
|
||||||
});
|
});
|
||||||
15
package.json
15
package.json
@@ -14,14 +14,25 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@astrojs/node": "^5.1.0",
|
||||||
"@astrojs/svelte": "^2.0.2",
|
"@astrojs/svelte": "^2.0.2",
|
||||||
"@astrojs/tailwind": "^3.1.0",
|
"@astrojs/tailwind": "^3.1.0",
|
||||||
"astro": "^2.0.16",
|
"astro": "^2.1.7",
|
||||||
|
"bcrypt": "^5.1.0",
|
||||||
|
"cookiejs": "^2.1.2",
|
||||||
|
"jwt-simple": "^0.5.6",
|
||||||
|
"knex": "^2.4.2",
|
||||||
|
"moment": "^2.29.4",
|
||||||
|
"pg": "^8.10.0",
|
||||||
"svelte": "^3.54.0",
|
"svelte": "^3.54.0",
|
||||||
"tailwindcss": "^3.0.24",
|
"tailwindcss": "^3.0.24",
|
||||||
"vitest": "^0.29.2"
|
"uuid": "^9.0.0",
|
||||||
|
"vitest": "^0.29.2",
|
||||||
|
"zod": "^3.21.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/bcrypt": "^5.0.0",
|
||||||
|
"@types/uuid": "^9.0.1",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.36.1",
|
"@typescript-eslint/eslint-plugin": "^5.36.1",
|
||||||
"@typescript-eslint/parser": "^5.36.1",
|
"@typescript-eslint/parser": "^5.36.1",
|
||||||
"astro": "^2.1.2",
|
"astro": "^2.1.2",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
>
|
>
|
||||||
<a class="headerButton" href="/energieausweis-kontakt.php">Kontakt</a>
|
<a class="headerButton" href="/energieausweis-kontakt.php">Kontakt</a>
|
||||||
<a class="headerButton" href="/agb.php">AGB</a>
|
<a class="headerButton" href="/agb.php">AGB</a>
|
||||||
<a class="headerButton" href="/user/energieausweis-login">Login</a>
|
<a class="headerButton" href="/login">Login</a>
|
||||||
<a class="hamburger_menu"
|
<a class="hamburger_menu"
|
||||||
><img src="/images/hamburger.png" alt="hamburger" /></a
|
><img src="/images/hamburger.png" alt="hamburger" /></a
|
||||||
>
|
>
|
||||||
|
|||||||
71
src/components/LoginView.svelte
Normal file
71
src/components/LoginView.svelte
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import cookie from "cookiejs"
|
||||||
|
|
||||||
|
let username: string;
|
||||||
|
let password: string;
|
||||||
|
let hasError: boolean;
|
||||||
|
|
||||||
|
async function login() {
|
||||||
|
const response = await fetch("/api/login", {
|
||||||
|
method: "POST",
|
||||||
|
body: JSON.stringify({
|
||||||
|
username, password
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const json = await response.json()
|
||||||
|
|
||||||
|
if (json.success == true) {
|
||||||
|
cookie.set("token", json.data.token);
|
||||||
|
cookie.set("expires", json.data.expires);
|
||||||
|
localStorage.setItem("token", json.data.token);
|
||||||
|
localStorage.setItem("expires", json.data.expires);
|
||||||
|
window.location.href = "/user";
|
||||||
|
} else {
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="width:50%;margin: 0 auto">
|
||||||
|
<h1>Login:</h1>
|
||||||
|
<div class="login_page">
|
||||||
|
{#if hasError}
|
||||||
|
<p>Das hat leider nicht geklappt, haben sie ihr Passwort und den Nutzernamen richtig eingegeben?</p>
|
||||||
|
{/if}
|
||||||
|
<div class="block_4" style="margin-top: 25px;">
|
||||||
|
<h4 class="heading_3">Benutzername</h4>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Benutzername"
|
||||||
|
name="username"
|
||||||
|
class="formInput"
|
||||||
|
bind:value={username}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="block_4">
|
||||||
|
<h4 class="heading_3">Passwort</h4>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="********"
|
||||||
|
name="password"
|
||||||
|
class="formInput"
|
||||||
|
bind:value={password}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 flex flex-row justify-between">
|
||||||
|
<button on:click={login}
|
||||||
|
>Einloggen</button
|
||||||
|
>
|
||||||
|
<a class="button"
|
||||||
|
href="/signup">Registrieren</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row justify-between" style="margin-top: 10px">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/user/passwort_vergessen">Passwort Vergessen?</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
74
src/components/RegisterView.svelte
Normal file
74
src/components/RegisterView.svelte
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
let username: string;
|
||||||
|
let password: string;
|
||||||
|
let email: string;
|
||||||
|
let hasError: boolean;
|
||||||
|
|
||||||
|
async function login() {
|
||||||
|
const response = await fetch("/api/user", {
|
||||||
|
method: "PUT",
|
||||||
|
body: JSON.stringify({
|
||||||
|
username, password, email
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const json = await response.json()
|
||||||
|
|
||||||
|
if (json.success == true) {
|
||||||
|
window.location.href = "/login";
|
||||||
|
} else {
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div style="width:50%;margin: 0 auto">
|
||||||
|
<h1>Registrieren:</h1>
|
||||||
|
<div class="login_page">
|
||||||
|
{#if hasError}
|
||||||
|
<p>Das hat leider nicht geklappt, haben sie ihr Passwort und den Nutzernamen richtig eingegeben?</p>
|
||||||
|
{/if}
|
||||||
|
<div class="block_4" style="margin-top: 25px;">
|
||||||
|
<h4 class="heading_3">Benutzername</h4>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Benutzername"
|
||||||
|
class="formInput"
|
||||||
|
bind:value={username}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="block_4" style="margin-top: 25px;">
|
||||||
|
<h4 class="heading_3">Email</h4>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Email"
|
||||||
|
class="formInput"
|
||||||
|
bind:value={email}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="block_4">
|
||||||
|
<h4 class="heading_3">Passwort</h4>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
placeholder="********"
|
||||||
|
class="formInput"
|
||||||
|
bind:value={password}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2 flex flex-row justify-between">
|
||||||
|
<button on:click={login}
|
||||||
|
>Registrieren</button
|
||||||
|
>
|
||||||
|
<a class="button"
|
||||||
|
href="/login">Einloggen</a
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row justify-between" style="margin-top: 10px">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/user/passwort_vergessen">Passwort Vergessen?</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@@ -101,4 +101,9 @@ const schema = JSON.stringify({
|
|||||||
body {
|
body {
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button, .button {
|
||||||
|
@apply px-8 py-2 bg-secondary rounded-lg text-white font-medium hover:shadow-lg transition-all hover:underline active:bg-blue-900;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
38
src/lib/APIResponse.ts
Normal file
38
src/lib/APIResponse.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import type { ZodError } from "zod";
|
||||||
|
|
||||||
|
export function success(data: any = {}) {
|
||||||
|
return new Response(JSON.stringify({
|
||||||
|
success: true,
|
||||||
|
data
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function error(errors: any[]) {
|
||||||
|
return new Response(JSON.stringify({
|
||||||
|
success: false,
|
||||||
|
errors
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function MissingPropertyError(properties: string[]) {
|
||||||
|
return error(properties.map(property => {
|
||||||
|
return `Missing property '${property}' in request body.`
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Signalisiert ein fehlendes Objekt und gibt den Fehler als HTTP Response zurück.
|
||||||
|
* @param name Der Name der Entität, die nicht gefunden werden konnte.
|
||||||
|
* @returns {Response} HTTP Response Objekt
|
||||||
|
*/
|
||||||
|
export function MissingEntityError(name: string): Response {
|
||||||
|
return error([`Missing entity, ${name} does not exist.`])
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ActionFailedError(): Response {
|
||||||
|
return error(["Failed executing action, error encountered."]);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InvalidDataError(err: ZodError): Response {
|
||||||
|
return error(err.issues);
|
||||||
|
}
|
||||||
9
src/lib/Ausweis/index.ts
Normal file
9
src/lib/Ausweis/index.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
export class Ausweis {
|
||||||
|
public static fromPublicId(public_id: string) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static fromPrivateId(id: number) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/lib/Ausweis/type.ts
Normal file
0
src/lib/Ausweis/type.ts
Normal file
10
src/lib/JsonWebToken.ts
Normal file
10
src/lib/JsonWebToken.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import * as jwt from "jwt-simple";
|
||||||
|
|
||||||
|
export function encodeToken(data: Record<string, any>) {
|
||||||
|
const token = jwt.encode(data, "yIvbgS$k7Bfc+mpV%TWDZAhje9#uJad4", "HS256");
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decodeToken(token: string) {
|
||||||
|
return jwt.decode(token, "yIvbgS$k7Bfc+mpV%TWDZAhje9#uJad4");
|
||||||
|
}
|
||||||
22
src/lib/Password.ts
Normal file
22
src/lib/Password.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import * as bcrypt from "bcrypt";
|
||||||
|
|
||||||
|
export async function hashPassword(password: string): Promise<string | null> {
|
||||||
|
const saltRounds = 4;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const salt = await bcrypt.genSalt(saltRounds);
|
||||||
|
const hash = await bcrypt.hash(password, salt);
|
||||||
|
return hash;
|
||||||
|
} catch(e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function validatePassword(known: string, unknown: string): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const compare = await bcrypt.compare(unknown, known)
|
||||||
|
return compare
|
||||||
|
} catch(e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
88
src/lib/User/index.ts
Normal file
88
src/lib/User/index.ts
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { db } from "../shared";
|
||||||
|
import { UserRegisterValidator, UserType, UserTypeValidator } from "./type";
|
||||||
|
import { v4 as uuid } from "uuid";
|
||||||
|
import { hashPassword } from "../Password";
|
||||||
|
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
/**
|
||||||
|
* Sucht einen Nutzer in der Datenbank anhand seiner Public/Unique id und gibt ihn zurück.
|
||||||
|
* @param uid Die unique/public id des gesuchten Benutzers.
|
||||||
|
* @returns {UserType | null} Die Daten des Nutzers oder null falls dieser nicht gefunden werden kann.
|
||||||
|
*/
|
||||||
|
public static async fromPublicId(uid: string): Promise<UserType | null> {
|
||||||
|
if (!uid || typeof uid !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await db<UserType>("users").select("*").where("uid", uid).first();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async fromUsername(username: string): Promise<UserType | null> {
|
||||||
|
if (!username || typeof username !== "string") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await db<UserType>("users").select("*").where("username", username).first();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sucht einen Nutzer in der Datenbank anhand seiner Private id und gibt ihn zurück.
|
||||||
|
* @param uid Die private id des gesuchten Benutzers.
|
||||||
|
* @returns {UserType | null} Die Daten des Nutzers oder null falls dieser nicht gefunden werden kann.
|
||||||
|
*/
|
||||||
|
public static async fromPrivateId(id: number): Promise<UserType | null> {
|
||||||
|
if (!id || typeof id !== "number") {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await db<UserType>("users").select("*").where("id", id).first();
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async create(user: UserType): Promise<{uid: string, id: number} | null> {
|
||||||
|
if (!user || UserRegisterValidator.safeParse(user).success == false) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uid = uuid();
|
||||||
|
const hashedPassword = await hashPassword(user.password);
|
||||||
|
|
||||||
|
if (!hashedPassword) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await db<UserType>("users").insert({
|
||||||
|
username: user.username,
|
||||||
|
email: user.email,
|
||||||
|
password: hashedPassword,
|
||||||
|
uid: uid
|
||||||
|
}, ["id"])
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
uid,
|
||||||
|
id: result[0].id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/lib/User/type.ts
Normal file
17
src/lib/User/type.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
|
export const UserTypeValidator = z.object({
|
||||||
|
username: z.string().min(4).max(64),
|
||||||
|
id: z.number(),
|
||||||
|
uid: z.string().length(36),
|
||||||
|
email: z.string().max(255),
|
||||||
|
password: z.string().min(6),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const UserRegisterValidator = z.object({
|
||||||
|
username: z.string().min(4).max(64),
|
||||||
|
email: z.string().max(255),
|
||||||
|
password: z.string().min(6),
|
||||||
|
})
|
||||||
|
|
||||||
|
export type UserType = z.infer<typeof UserTypeValidator>
|
||||||
19
src/lib/shared.ts
Normal file
19
src/lib/shared.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import knex, { Knex } from "knex";
|
||||||
|
|
||||||
|
export function dbOpen(): Knex {
|
||||||
|
const db = knex({
|
||||||
|
client: 'pg',
|
||||||
|
connection: {
|
||||||
|
host : '127.0.0.1',
|
||||||
|
port : 5436,
|
||||||
|
user : 'main',
|
||||||
|
password : 'hHMP8cd^N3SnzGRR',
|
||||||
|
database : 'main'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return db;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export const db = dbOpen();
|
||||||
34
src/pages/api/login.ts
Normal file
34
src/pages/api/login.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import type { APIRoute } from "astro";
|
||||||
|
import { success, MissingPropertyError, MissingEntityError, ActionFailedError, InvalidDataError, error } from "../../lib/APIResponse";
|
||||||
|
import { validatePassword } from "../../lib/Password";
|
||||||
|
import { User } from "../../lib/User";
|
||||||
|
import moment from "moment";
|
||||||
|
import { encodeToken } from "../../lib/JsonWebToken";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ruft einen Nutzer anhand seiner uid aus der Datenbank ab.
|
||||||
|
* @param param0 Die Request mit dem request body. Dieser enthält entweder eine uid mit der der Benutzer identifiziert werden kann.
|
||||||
|
*/
|
||||||
|
export const post: APIRoute = async ({ request }) => {
|
||||||
|
const body = await request.json();
|
||||||
|
|
||||||
|
if (!body.hasOwnProperty("username") || !body.hasOwnProperty("password")) {
|
||||||
|
return MissingPropertyError(["username", "password"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = await User.fromUsername(body.username);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return error(["Invalid username or password."]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate Password
|
||||||
|
if (!validatePassword(user.password, body.password)) {
|
||||||
|
return error(["Invalid username or password."]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expiry = moment().add(2, "days").unix();
|
||||||
|
const token = encodeToken({ id: user.id, uid: user.uid, exp: expiry })
|
||||||
|
|
||||||
|
return success({ token, expires: expiry });
|
||||||
|
}
|
||||||
43
src/pages/api/user.ts
Normal file
43
src/pages/api/user.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import type { APIRoute } from "astro";
|
||||||
|
import { success, MissingPropertyError, MissingEntityError, ActionFailedError, InvalidDataError } from "../../lib/APIResponse";
|
||||||
|
import { User } from "../../lib/User";
|
||||||
|
import { UserRegisterValidator, UserType, UserTypeValidator } from "../../lib/User/type";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ruft einen Nutzer anhand seiner uid aus der Datenbank ab.
|
||||||
|
* @param param0 Die Request mit dem request body. Dieser enthält entweder eine uid mit der der Benutzer identifiziert werden kann.
|
||||||
|
*/
|
||||||
|
export const get: APIRoute = async ({ request }) => {
|
||||||
|
const body = await request.json();
|
||||||
|
|
||||||
|
if (!body.hasOwnProperty("uid")) {
|
||||||
|
return MissingPropertyError(["uid"]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = User.fromPublicId(body.uid);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return MissingEntityError("user");
|
||||||
|
}
|
||||||
|
|
||||||
|
return success(user);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const put: APIRoute = async ({ request }) => {
|
||||||
|
const body = await request.json();
|
||||||
|
|
||||||
|
const validate = UserRegisterValidator.safeParse(body);
|
||||||
|
|
||||||
|
if (validate.success == false) {
|
||||||
|
return InvalidDataError(validate.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await User.create(body as UserType);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
return ActionFailedError();
|
||||||
|
}
|
||||||
|
|
||||||
|
return success({ uid: result.uid, id: result.id });
|
||||||
|
}
|
||||||
|
|
||||||
17
src/pages/login.astro
Normal file
17
src/pages/login.astro
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
import moment from "moment";
|
||||||
|
import LoginView from "../components/LoginView.svelte";
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
|
||||||
|
const token = Astro.cookies.get("token").value;
|
||||||
|
const expires = Astro.cookies.get("expires").number();
|
||||||
|
|
||||||
|
const now = moment().unix();
|
||||||
|
if (token && now < expires) {
|
||||||
|
return Astro.redirect("/user");
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Login">
|
||||||
|
<LoginView client:only></LoginView>
|
||||||
|
</Layout>
|
||||||
17
src/pages/signup.astro
Normal file
17
src/pages/signup.astro
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
---
|
||||||
|
import moment from "moment";
|
||||||
|
import RegisterView from "../components/RegisterView.svelte";
|
||||||
|
import Layout from "../layouts/Layout.astro";
|
||||||
|
|
||||||
|
const token = Astro.cookies.get("token").value;
|
||||||
|
const expires = Astro.cookies.get("expires").number();
|
||||||
|
|
||||||
|
const now = moment().unix();
|
||||||
|
if (token && now < expires) {
|
||||||
|
return Astro.redirect("/user");
|
||||||
|
}
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Login">
|
||||||
|
<RegisterView client:only></RegisterView>
|
||||||
|
</Layout>
|
||||||
63
src/pages/user/index.astro
Normal file
63
src/pages/user/index.astro
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
---
|
||||||
|
import moment from "moment";
|
||||||
|
import Layout from "../../layouts/Layout.astro"
|
||||||
|
import { decodeToken } from "../../lib/JsonWebToken";
|
||||||
|
import { User } from "../../lib/User";
|
||||||
|
|
||||||
|
const token = Astro.cookies.get("token").value;
|
||||||
|
const expires = Astro.cookies.get("expires").number();
|
||||||
|
|
||||||
|
const now = moment().unix();
|
||||||
|
if (!token || now > expires) {
|
||||||
|
Astro.cookies.delete("token");
|
||||||
|
Astro.cookies.delete("expires");
|
||||||
|
return Astro.redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsed = decodeToken(token);
|
||||||
|
const user = await User.fromPublicId(parsed.uid);
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
return Astro.redirect("/login");
|
||||||
|
}
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<Layout title="Dashboard">
|
||||||
|
<div class="profile_container">
|
||||||
|
<div><h1>Willkommen zurück <b>{user.username}</b></h1>
|
||||||
|
<div id="searchBoxContainer" class="flex-row" style="gap: 5px; margin: 10px 0;"></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-column" style="gap: 10px;">
|
||||||
|
<a href="/user/settings" class="weiterbutton">Account Details Ändern</a>
|
||||||
|
<a href="/logout" class="weiterbutton">Ausloggen</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row filter-bar" style="grid-area: 2/1/2/3; min-height: 0; gap: 20px;">
|
||||||
|
<div class="flex-row" style="width: 100%;">
|
||||||
|
<a onclick="addSort()" class='weiterbutton'>Filter Hinzufügen</a>
|
||||||
|
<a onclick="window.Offset > 0 ? (() => {
|
||||||
|
GetData(window.Offset - 1)
|
||||||
|
})() : null" class='weiterbutton'>Vorherige Seite</a>
|
||||||
|
<a onclick="window.Offset >= 0 ? (() => {
|
||||||
|
GetData(window.Offset + 1)
|
||||||
|
})() : null" class='weiterbutton'>Nächste Seite</a>
|
||||||
|
<a onclick="window.Offset > 0 ? (() => {
|
||||||
|
GetData(0)
|
||||||
|
})() : null" class='weiterbutton'>Erste Seite</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex-row" style="width: 100%;">
|
||||||
|
<select onchange="window.GetLength = parseInt(this.value); GetData(0);">
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="75">75</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
</select>
|
||||||
|
<select onchange="window.orderDirection = this.value; GetData(0);">
|
||||||
|
<option value="DESC">Absteigend</option>
|
||||||
|
<option value="ASC">Aufsteigend</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='login-container' id="items-container"></div>
|
||||||
|
</div>
|
||||||
|
</Layout>
|
||||||
Reference in New Issue
Block a user