Compare commits

..

3 Commits

56 changed files with 659 additions and 169 deletions

4
src/app.d.ts vendored
View File

@ -1,9 +1,5 @@
// See https://kit.svelte.dev/docs/types#app // See https://kit.svelte.dev/docs/types#app
// for information about these interfaces // for information about these interfaces
declare module '@fortawesome/pro-solid-svg-icons/index.es' {
export * from '@fortawesome/pro-solid-svg-icons';
}
declare global { declare global {
namespace App { namespace App {
// interface Error {} // interface Error {}

28
src/hooks.server.ts Normal file
View File

@ -0,0 +1,28 @@
import { authenticateUser } from "$lib/js/authenticate"
import type { Handle } from "@sveltejs/kit"
import { redirect } from "@sveltejs/kit"
import { error } from "@sveltejs/kit"
export const handle : Handle = async({event, resolve}) => {
event.locals.user = await authenticateUser(event.cookies)
if(event.url.pathname.startsWith('/rezepte/edit') || event.url.pathname.startsWith('/rezepte/add')){
if(!event.locals.user){
throw redirect(303, "/login")
}
else if(!event.locals.user.access.includes("rezepte")){
throw error(401, "Your user does not have access to this page")
}
}
else if(event.url.pathname.startsWith('/abrechnung')){
console.log(event.locals.user)
if(!event.locals.user){
throw redirect(303, "/login")
}
else if(!event.locals.user.access.includes("abrechnung")){
throw error(401, "Your User does not have access to this page")
}
}
const response = await resolve(event)
return response
}

View File

@ -111,9 +111,6 @@ export function edit_ingredient_and_close_modal(){
modal_el.close(); modal_el.close();
} }
export function show_keys(event){
console.log(event.ctrlKey, event.key)
}
</script> </script>
<style> <style>

View File

@ -122,7 +122,6 @@
const item = await res.json(); const item = await res.json();
} }
async function doAdd () { async function doAdd () {
console.log(add_info.total_time)
const res = await fetch('/api/add', { const res = await fetch('/api/add', {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({

View File

@ -14,7 +14,6 @@ const link_els = document.querySelectorAll("nav a")
link_els.forEach((el) => { link_els.forEach((el) => {
el.addEventListener("click", () => {toggle_sidebar(true)}); el.addEventListener("click", () => {toggle_sidebar(true)});
}) })
console.log(link_els)
}) })
</script> </script>

View File

@ -229,7 +229,7 @@ span
<button class:selected={multiplier==1.5} on:click={() => {multiplier=1.5; custom_mul="…"}}><sup>3</sup>&frasl;<sub>2</sub>x</button> <button class:selected={multiplier==1.5} on:click={() => {multiplier=1.5; custom_mul="…"}}><sup>3</sup>&frasl;<sub>2</sub>x</button>
<button class:selected={multiplier==2} on:click="{() => {multiplier=2; custom_mul="…"}}">2x</button> <button class:selected={multiplier==2} on:click="{() => {multiplier=2; custom_mul="…"}}">2x</button>
<button class:selected={multiplier==3} on:click="{() => {multiplier=3; custom_mul="…"}}">3x</button> <button class:selected={multiplier==3} on:click="{() => {multiplier=3; custom_mul="…"}}">3x</button>
<button class:selected={multiplier==custom_mul} on:click={(e) => { console.log(e) ;const el = e.composedPath()[0].children[0]; if(el){ el.focus()}}}> <button class:selected={multiplier==custom_mul} on:click={(e) => { const el = e.composedPath()[0].children[0]; if(el){ el.focus()}}}>
<span class:selected={multiplier==custom_mul} <span class:selected={multiplier==custom_mul}
on:focus={() => { custom_mul="" } on:focus={() => { custom_mul="" }
} }

View File

@ -47,7 +47,6 @@
const json = await res.json() const json = await res.json()
result = JSON.stringify(json) result = JSON.stringify(json)
console.log(result)
} }
</script> </script>
<style> <style>

View File

@ -5,7 +5,6 @@
let months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"] let months = ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"]
let month : number; let month : number;
export let active_index; export let active_index;
console.log(active_index)
</script> </script>
<style> <style>

View File

@ -0,0 +1,39 @@
import type { RequestEvent } from "@sveltejs/kit";
import { COOKIE_SECRET } from "$env/static/private";
import { verify } from "jsonwebtoken";
import { error } from "@sveltejs/kit";
import { dbConnect, dbDisconnect } from "../../utils/db";
import { User } from "../../models/User";;
export async function authenticateUser(cookies){
// Set your master secret key (replace with your own secret)
const masterSecret = COOKIE_SECRET;
const secretKey = masterSecret
let decoded
try{
const cookie : string = cookies.get("UserSession")
if(cookie){
decoded = await verify(cookie, secretKey);
}
}
catch(e){
return null
}
if(decoded){
await dbConnect()
let res = await User.findOne({username: decoded.username}, 'access').lean();
await dbDisconnect()
if(!res){
throw error(404, "User for this Cookie does no longer exist")
}
return {
username: decoded.username,
access: res.access
}
}
else{
return null
}
}

12
src/models/User.ts Normal file
View File

@ -0,0 +1,12 @@
import mongoose from 'mongoose';
const UserSchema = new mongoose.Schema(
{
username: {type: String, required: true, unique: true},
pass_hash: {type: String, required: true},
salt : {type: String, required: true},
access: [String], //rezepte, flims, abrechnung, ...
}, {timestamps: true}
);
export const User = mongoose.model("User", UserSchema);

View File

@ -0,0 +1,14 @@
<script>
import Header from '$lib/components/Header.svelte'
</script>
<Header>
<ul class=site_header slot=links>
<li><a href="/rezepte">Rezepte</a></li>
<li><a href="/bilder">Bilder</a></li>
<li><a href="/git">Git</a></li>
<li><a href="/transmission">Transmission</a></li>
</ul>
<slot></slot>
</Header>

View File

@ -0,0 +1,18 @@
<style>
</style>
<section>
<h2><a href="/rezepte">Rezepte</a></h2>
</section>
<section>
<h2><a href="/bilder">Bilder</a></h2>
</section>
<section>
<h2><a href="/git">Git</a></h2>
</section>
<section>
<h2><a href="/transmission">Transmission Web Viewer</a></h2>
</section>

View File

@ -0,0 +1,10 @@
<script lang="ts">
import Header from "$lib/components/Header.svelte";
</script>
<Header>
<ul class=site_header slot=links>
<li><a href="/">Home</a></li>
</ul>
</Header>

View File

@ -0,0 +1,16 @@
<script lang="ts">
import Header from "$lib/components/Header.svelte";
import Calendar from "$lib/components/Calendar.svelte";
</script>
<Header>
<ul class=site_header slot=links>
<li><a href="/">Home</a></li>
</ul>
<Calendar>
</Calendar>
</Header>

View File

@ -0,0 +1,41 @@
import { redirect } from "@sveltejs/kit"
import type { Actions, PageServerLoad } from "./$types"
import { error } from "@sveltejs/kit"
export const load: PageServerLoad = async ({ locals }) => {
return {
user: locals.user,
}
}
export const actions: Actions = {
login: async (event) => {
const data = await event.request.formData()
const res = await event.fetch('/api/login',
{method: 'POST',
body: JSON.stringify({
username: data.get('username'),
password: data.get('password'),
})
}
)
const jwt = await res.json()
if(res.ok){
event.cookies.set("UserSession", jwt, {
path: "/",
httpOnly: true,
sameSite: "strict",
secure: process.env.NODE_ENV === "production",
maxAge: 60 * 60 * 24 * 7, // 1 week
})
throw redirect(303, "/")
}
else{
throw error(401, jwt.message)
}
},
logout: async () => {
throw redirect(303, "/logout")
},
}

View File

@ -0,0 +1,13 @@
<h1>Log In</h1>
<form action="?/login" method=POST>
<label>
Username
<input type="text" name="username">
</label>
<label>
Passwort
<input name="password" type="password">
</label>
<button type="submit">Log In</button>
</form>

View File

@ -0,0 +1,7 @@
import { redirect } from "@sveltejs/kit"
import type { Actions, PageServerLoad } from "./$types"
export const load: PageServerLoad = async ({ cookies }) => {
cookies.delete("UserSession")
redirect(303, "/")
}

View File

@ -0,0 +1,12 @@
<script>
import { redirect } from "@sveltejs/kit";
import { afterNavigate } from '$app/navigation';
import { onMount } from "svelte";
afterNavigate(() => {
redirect(303, "/")
})
onMount(() => {
redirect(303, "/")
})
</script>
<h1>Log Out</h1>

View File

@ -0,0 +1,33 @@
import { redirect } from "@sveltejs/kit"
import type { Actions, PageServerLoad } from "./$types"
export const load: PageServerLoad = async ({ locals }) => {
return {
user: locals.user,
}
}
export const actions: Actions = {
register: async (event) => {
const data = await event.request.formData();
const acccess_options = ["rezepte", "abrechnung", "flims"]
let enabled_access = []
acccess_options.forEach((option) => {
if(data.get(option) == 'on'){
enabled_access.push(option)
}
})
const res = await event.fetch('/api/register',
{method: 'POST',
body: JSON.stringify({
username: data.get('username'),
password: data.get('password'),
access: enabled_access,
})
}
)
throw redirect(303, "/login")
},
}

View File

@ -0,0 +1,44 @@
<script>
import { setCookie } from 'svelte-cookie';
export async function createJWT() {
const res = await fetch('/api/login',
{method: 'POST',
body: JSON.stringify({
username: "testuser2",
password: "password",
})
}
)
const jwt = await res.json()
setCookie('UserSession', jwt, {expires: 7})
}
</script>
<style>
</style>
<h1>Register</h1>
<form action="?/register" method=POST>
<label>
Username
<input type="text" name="username">
</label>
<label>
Passwort
<input name="password" type="password">
</label>
<label>
Rezepte
<input type="checkbox" name="rezepte">
</label>
<label>
Abrechnungen
<input type="checkbox" name="abrechnung">
</label>
<label>
Flims
<input type="checkbox" name="flims">
</label>
<button type="submit">Register</button>
</form>

View File

@ -0,0 +1,78 @@
<script>
import Header from '$lib/components/Header.svelte'
import { onMount } from 'svelte';
import { goto } from '$app/navigation';
import { get } from 'svelte/store';
import { setCookie } from 'svelte-cookie';
export async function createJWT() {
const res = await fetch('/api/login',
{method: 'POST',
body: JSON.stringify({
username: "testuser2",
password: "password",
})
}
)
const jwt = await res.json()
setCookie('UserSession', jwt, {expires: 7})
}
export async function registerUserTest(){
const res = await fetch('/api/register',
{method: 'POST',
body: JSON.stringify({
username: "testuser2",
password: "password",
access: ["rezepte", "abrechnung", "flims" ]
})
}
)
console.log("res:", res);
const j = await res.json()
console.log("response:", j)
}
export async function readJWTSS(){
const res = await fetch('/api/verify',
{method: 'GET',
credentials: 'include',
}
)
const item = await res.json()
console.log(res)
console.log(item)
}
</script>
<style>
</style>
<Header>
<ul class=site_header slot=links>
<li><a href="/rezepte">Rezepte</a></li>
<li><a href="/bilder">Bilder</a></li>
<li><a href="/git">Git</a></li>
<li><a href="/transmission">Transmission</a></li>
</ul>
<section>
<h2><a href="/rezepte">Rezepte</a></h2>
</section>
<section>
<h2><a href="/bilder">Bilder</a></h2>
</section>
<section>
<h2><a href="/git">Git</a></h2>
</section>
<section>
<h2><a href="/transmission">Transmission Web Viewer</a></h2>
</section>
<button on:click={registerUserTest}>Test User Registration</button>
<button on:click={createJWT}>Log In</button>
<button on:click={readJWTSS}>Test reading cookie</button>
</Header>

View File

@ -1,6 +1,6 @@
import type { PageLoad } from "./$types"; import type { PageServerLoad } from "./$types";
export async function load({ fetch }) { export async function load({ fetch, locals }) {
let current_month = new Date().getMonth() + 1 let current_month = new Date().getMonth() + 1
const res_season = await fetch(`/api/items/in_season/` + current_month); const res_season = await fetch(`/api/items/in_season/` + current_month);
const res_all_brief = await fetch(`/api/items/all_brief`); const res_all_brief = await fetch(`/api/items/all_brief`);
@ -9,5 +9,6 @@ export async function load({ fetch }) {
return { return {
season: item_season, season: item_season,
all_brief: item_all_brief, all_brief: item_all_brief,
user: locals.user,
}; };
}; };

View File

@ -35,4 +35,6 @@ h1{
</MediaScroller> </MediaScroller>
{/each} {/each}
<p>{data.all_brief.length}</p> <p>{data.all_brief.length}</p>
<AddButton></AddButton> {#if data.user && data.user.access.includes("rezepte")}
<AddButton></AddButton>
{/if}

View File

@ -4,9 +4,10 @@ import type { PageLoad } from "./$types";
//import { dbConnect, dbDisconnect } from '../../../utils/db'; //import { dbConnect, dbDisconnect } from '../../../utils/db';
import { error } from "@sveltejs/kit"; import { error } from "@sveltejs/kit";
export async function load({ fetch, params }) { export async function load({ fetch, params, locals }) {
const res = await fetch(`/api/items/${params.name}`); const res = await fetch(`/api/items/${params.name}`);
const item = await res.json(); let item = await res.json();
item.user = locals.user
if(res.status != 200){ if(res.status != 200){
throw error(res.status, item.message) throw error(res.status, item.message)
} }

View File

@ -9,7 +9,7 @@
import IngredientsPage from '$lib/components/IngredientsPage.svelte'; import IngredientsPage from '$lib/components/IngredientsPage.svelte';
import TitleImgParallax from '$lib/components/TitleImgParallax.svelte'; import TitleImgParallax from '$lib/components/TitleImgParallax.svelte';
import { afterNavigate } from '$app/navigation'; import { afterNavigate } from '$app/navigation';
import {season} from '$lib/js/season_store'; import {season} from '$lib/js/season_store';
export let data: PageData; export let data: PageData;
@ -242,4 +242,6 @@ h4{
</div> </div>
</TitleImgParallax> </TitleImgParallax>
{#if data.user && data.user.access.includes("rezepte")}
<EditButton href="/rezepte/edit/{data.short_name}"></EditButton> <EditButton href="/rezepte/edit/{data.short_name}"></EditButton>
{/if}

View File

@ -0,0 +1,5 @@
export async function load({locals}) {
return {
user: locals.user
};
};

View File

@ -47,7 +47,6 @@
cooking: "", cooking: "",
} }
let password = ""
let images = [] let images = []
let short_name = "" let short_name = ""
let datecreated = new Date() let datecreated = new Date()
@ -86,14 +85,13 @@
const data = { const data = {
image: img_local, image: img_local,
name: short_name, name: short_name,
bearer: password,
} }
await fetch(`/api/img/add`, { await fetch(`/api/img/add`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Accept: 'application/json', Accept: 'application/json',
bearer: password, credentials: 'include',
}, },
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
@ -241,6 +239,25 @@ h1{
h3{ h3{
text-align: center; text-align: center;
} }
button.action_button{
animation: unset !important;
font-size: 1.3rem;
color: white;
}
.submit_buttons{
display: flex;
margin-inline: auto;
max-width: 1000px;
margin-block: 1rem;
justify-content: center;
align-items: center;
gap: 2rem;
}
.submit_buttons p{
padding: 0;
padding-right: 0.5em;
margin: 0;
}
</style> </style>
<h1>Rezept erstellen</h1> <h1>Rezept erstellen</h1>
@ -275,8 +292,6 @@ h3{
<div class=addendum bind:innerText={addendum} contenteditable></div> <div class=addendum bind:innerText={addendum} contenteditable></div>
</div> </div>
<div class=submit_wrapper> <div class=submit_buttons>
<h2>Neues Rezept hinzufügen:</h2> <button class=action_button on:click={doPost}><p>Hinzufügen</p><Check fill=white width=2rem height=2rem></Check></button>
<input type="password" placeholder=Passwort bind:value={password}>
<button class=action_button on:click={doPost}><Check fill=white width=2rem height=2rem></Check></button>
</div> </div>

View File

@ -1,8 +1,10 @@
import type { PageLoad } from "./$types"; import type { PageLoad } from "./$types";
export async function load({ fetch, params}) { export async function load({ fetch, params, locals}) {
let current_month = new Date().getMonth() + 1 let current_month = new Date().getMonth() + 1
const res = await fetch(`/api/items/${params.name}`); const res = await fetch(`/api/items/${params.name}`);
const recipe = await res.json(); const recipe = await res.json();
return {recipe}; return {recipe: recipe,
user: locals.user
};
}; };

View File

@ -59,7 +59,6 @@
let images = data.recipe.images let images = data.recipe.images
let short_name = data.recipe.short_name let short_name = data.recipe.short_name
let password
let datecreated = data.recipe.datecreated let datecreated = data.recipe.datecreated
let datemodified = new Date() let datemodified = new Date()
@ -99,10 +98,10 @@
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name: old_short_name, name: old_short_name,
bearer: password,}), }),
headers : { headers : {
'content-type': 'application/json', 'content-type': 'application/json',
bearer: password credentials: 'include',
} }
}) })
if(!res_img.ok){ if(!res_img.ok){
@ -117,7 +116,6 @@
old_short_name, old_short_name,
headers: { headers: {
'content-type': 'application/json', 'content-type': 'application/json',
bearer: password,
} }
}) })
@ -145,11 +143,10 @@
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
name: old_short_name, name: old_short_name,
bearer: password,
}), }),
headers : { headers : {
'content-type': 'application/json', 'content-type': 'application/json',
bearer: password credentials: 'include',
} }
}) })
if(!res.ok){ if(!res.ok){
@ -161,14 +158,13 @@
const data = { const data = {
image: img_local, image: img_local,
name: short_name, name: short_name,
bearer: password,
} }
const res = await fetch(`/api/img/add`, { const res = await fetch(`/api/img/add`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Accept: 'application/json', Accept: 'application/json',
bearer: password, credentials: 'include',
}, },
body: JSON.stringify(data) body: JSON.stringify(data)
}); });
@ -183,18 +179,16 @@
// case new short_name: // case new short_name:
else if(short_name != old_short_name){ else if(short_name != old_short_name){
console.log("MOVING") console.log("MOVING")
console.log("PASSWORD:", password)
const res_img = await fetch('/api/img/mv', { const res_img = await fetch('/api/img/mv', {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
Accept: 'application/json', Accept: 'application/json',
bearer: password, credentials: 'include',
}, },
body: JSON.stringify({ body: JSON.stringify({
old_name: old_short_name, old_name: old_short_name,
new_name: short_name, new_name: short_name,
bearer: password,
}) })
}) })
if(!res_img.ok){ if(!res_img.ok){
@ -223,7 +217,7 @@
old_short_name, old_short_name,
headers: { headers: {
'content-type': 'application/json', 'content-type': 'application/json',
bearer: password, credentials: 'include',
} }
}) })
}) })
@ -270,26 +264,6 @@ input:focus-visible
flex-direction: column; flex-direction: column;
} }
} }
input[type=password]{
box-sizing: border-box;
font-size: 1.5rem;
padding-block: 0.5em;
display: inline;
width: 100%;
}
.submit_wrapper{
position: relative;
margin-inline: auto;
width: max(300px, 50vw)
}
.submit_wrapper button{
position: absolute;
right:-1em;
bottom: -0.5em;
}
.submit_wrapper h2{
margin-bottom: 0;
}
h1{ h1{
text-align: center; text-align: center;
margin-bottom: 2rem; margin-bottom: 2rem;
@ -340,9 +314,27 @@ h1{
h3{ h3{
text-align: center; text-align: center;
} }
button.action_button{
animation: unset !important;
font-size: 1.3rem;
color: white;
}
.submit_buttons{
display: flex;
margin-inline: auto;
max-width: 1000px;
margin-block: 1rem;
justify-content: center;
align-items: center;
gap: 2rem;
}
.submit_buttons p{
padding: 0;
padding-right: 0.5em;
margin: 0;
}
</style> </style>
<h1>Rezept editieren</h1> <h1>Rezept editieren</h1>
<CardAdd {card_data} {image_preview_url} ></CardAdd> <CardAdd {card_data} {image_preview_url} ></CardAdd>
<h3>Kurzname (für URL):</h3> <h3>Kurzname (für URL):</h3>
@ -374,14 +366,7 @@ h3{
<div class=addendum bind:innerText={addendum} contenteditable></div> <div class=addendum bind:innerText={addendum} contenteditable></div>
</div> </div>
<div class=submit_wrapper> <div class=submit_buttons>
<h2>Editiertes Rezept abspeichern:</h2> <button class=action_button on:click={doDelete}><p>Löschen</p><Cross fill=white width=2rem height=2rem></Cross></button>
<input type="password" placeholder=Passwort bind:value={password}> <button class=action_button on:click={doEdit}><p>Speichern</p><Check fill=white width=2rem height=2rem></Check></button>
<button class=action_button on:click={doEdit}><Check fill=white width=2rem height=2rem></Check></button>
</div>
<div class=submit_wrapper>
<h2>Rezept löschen:</h2>
<input type="password" placeholder=Passwort bind:value={password}>
<button class=action_button on:click={doDelete}><Cross fill=white width=2rem height=2rem></Cross></button>
</div> </div>

View File

@ -1,32 +0,0 @@
<script>
import Header from '$lib/components/Header.svelte'
</script>
<style>
</style>
<Header>
<ul class=site_header slot=links>
<li><a href="/rezepte">Rezepte</a></li>
<li><a href="/bilder">Bilder</a></li>
<li><a href="/git">Git</a></li>
<li><a href="/transmission">Transmission</a></li>
</ul>
<section>
<h2><a href="/rezepte">Rezepte</a></h2>
</section>
<section>
<h2><a href="/bilder">Bilder</a></h2>
</section>
<section>
<h2><a href="/git">Git</a></h2>
</section>
<section>
<h2><a href="/transmission">Transmission Web Viewer</a></h2>
</section>
</Header>

View File

@ -1,28 +1,30 @@
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { Recipe } from '../../../models/Recipe'; import { Recipe } from '../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../utils/db'; import { dbConnect, dbDisconnect } from '../../../utils/db';
import type {RecipeModelType} from '../../../types/types';
import { BEARER_TOKEN } from '$env/static/private'
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';;
// header: use for bearer token for now // header: use for bearer token for now
// recipe json in body // recipe json in body
export const POST: RequestHandler = async ({request}) => { export const POST: RequestHandler = async ({request, cookies}) => {
let message = await request.json() let message = await request.json()
const recipe_json = message.recipe const recipe_json = message.recipe
const bearer_token = message.headers.bearer const user = await authenticateUser(cookies)
if(bearer_token === BEARER_TOKEN){ if(!user){
await dbConnect(); throw error(401, "Not logged in")
try{ }
await Recipe.create(recipe_json); if(!user.access.includes("rezepte")){
} catch(e){ throw error(401, "This user does not have permissions to add recipes")
throw error(400, e) }
} else{
await dbDisconnect(); await dbConnect();
return new Response(JSON.stringify({msg: "Added recipe successfully"}),{ try{
status: 200, await Recipe.create(recipe_json);
}); } catch(e){
} throw error(400, e)
else{ }
throw error(403, "Password incorrect") await dbDisconnect();
} return new Response(JSON.stringify({msg: "Added recipe successfully"}),{
status: 200,
});
}
}; };

View File

@ -2,23 +2,22 @@ import type { RequestHandler } from '@sveltejs/kit';
import { Recipe } from '../../../models/Recipe'; import { Recipe } from '../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../utils/db'; import { dbConnect, dbDisconnect } from '../../../utils/db';
import type {RecipeModelType} from '../../../types/types'; import type {RecipeModelType} from '../../../types/types';
import { BEARER_TOKEN } from '$env/static/private'
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
// header: use for bearer token for now // header: use for bearer token for now
// recipe json in body // recipe json in body
export const POST: RequestHandler = async ({request}) => { export const POST: RequestHandler = async ({request, cookies}) => {
let message = await request.json() let message = await request.json()
const short_name = message.old_short_name
const bearer_token = message.headers.bearer const user = await authenticateUser(cookies)
if(bearer_token === BEARER_TOKEN){ if(!user) throw error(401, "Need to be logged in")
await dbConnect(); if(!user.access.includes("rezepte")) throw error(401, "Insufficient permissions")
const short_name = message.old_short_name
await dbConnect();
await Recipe.findOneAndDelete({short_name: short_name}); await Recipe.findOneAndDelete({short_name: short_name});
await dbDisconnect(); await dbDisconnect();
return new Response(JSON.stringify({msg: "Deleted recipe successfully"}),{ return new Response(JSON.stringify({msg: "Deleted recipe successfully"}),{
status: 200, status: 200,
}); });
}
else{
throw error(403, "Password incorrect")
}
} }

View File

@ -2,16 +2,23 @@ import type { RequestHandler } from '@sveltejs/kit';
import { Recipe } from '../../../models/Recipe'; import { Recipe } from '../../../models/Recipe';
import { dbConnect, dbDisconnect } from '../../../utils/db'; import { dbConnect, dbDisconnect } from '../../../utils/db';
import type {RecipeModelType} from '../../../types/types'; import type {RecipeModelType} from '../../../types/types';
import { BEARER_TOKEN } from '$env/static/private'
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
// header: use for bearer token for now // header: use for bearer token for now
// recipe json in body // recipe json in body
export const POST: RequestHandler = async ({request}) => { export const POST: RequestHandler = async ({request, cookies}) => {
let message = await request.json() let message = await request.json()
const recipe_json = message.recipe const recipe_json = message.recipe
const bearer_token = message.headers.bearer const user = await authenticateUser(cookies)
if(bearer_token === BEARER_TOKEN){ console.log(user)
await dbConnect(); if(!user){
throw error(403, "Not logged in")
}
else if(!user.access.includes("rezepte")){
throw error(403, "This user does not have edit permissions for recipes")
}
else{
await dbConnect();
await Recipe.findOneAndUpdate({short_name: message.old_short_name }, recipe_json); await Recipe.findOneAndUpdate({short_name: message.old_short_name }, recipe_json);
await dbDisconnect(); await dbDisconnect();
return new Response(JSON.stringify({msg: "Edited recipe successfully"}),{ return new Response(JSON.stringify({msg: "Edited recipe successfully"}),{
@ -19,7 +26,4 @@ export const POST: RequestHandler = async ({request}) => {
}); });
} }
else{
throw error(403, "Password incorrect")
}
}; };

View File

@ -1,13 +1,15 @@
import path from 'path' import path from 'path'
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { BEARER_TOKEN } from '$env/static/private'
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { IMAGE_DIR } from '$env/static/private' import { IMAGE_DIR } from '$env/static/private'
import sharp from 'sharp'; import sharp from 'sharp';
import { authenticateUser } from '$lib/js/authenticate';
export const POST = (async ({ request }) => { export const POST = (async ({ request, cookies }) => {
const data = await request.json(); const data = await request.json();
if(data.bearer === BEARER_TOKEN){ const user = await authenticateUser(cookies)
if (!user) throw error(401, "Need to be logged in")
if (!user.access.includes("rezepte")) throw error(401, "You don't have sufficient permissions for this")
let full_res = new Buffer.from(data.image, 'base64') let full_res = new Buffer.from(data.image, 'base64')
// reduce image size if over 500KB // reduce image size if over 500KB
const MAX_SIZE_KB = 500 const MAX_SIZE_KB = 500
@ -41,9 +43,4 @@ export const POST = (async ({ request }) => {
return new Response(JSON.stringify({msg: "Added image successfully"}),{ return new Response(JSON.stringify({msg: "Added image successfully"}),{
status: 200, status: 200,
}); });
}
else{
throw error(403, "Password incorrect")
}
}) satisfies RequestHandler; }) satisfies RequestHandler;

View File

@ -1,13 +1,15 @@
import path from 'path' import path from 'path'
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { BEARER_TOKEN } from '$env/static/private'
import { IMAGE_DIR } from '$env/static/private' import { IMAGE_DIR } from '$env/static/private'
import { unlink } from 'node:fs'; import { unlink } from 'node:fs';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';;
export const POST = (async ({ request }) => { export const POST = (async ({ request, cookies }) => {
const data = await request.json(); const data = await request.json();
if(data.bearer === BEARER_TOKEN){ const user = await authenticateUser(cookies)
if(!user) throw error(401, "You need to be logged in")
if(!user.access.includes("rezepte")) throw error(401, "Your don't have the required permission for this")
[ "full", "thumb", "placeholder"].forEach((folder) => { [ "full", "thumb", "placeholder"].forEach((folder) => {
unlink(path.join(IMAGE_DIR, "rezepte", folder, data.name + ".webp"), (e) => { unlink(path.join(IMAGE_DIR, "rezepte", folder, data.name + ".webp"), (e) => {
if(e) error(404, "could not delete: " + folder + "/" + data.name + ".webp" + e) if(e) error(404, "could not delete: " + folder + "/" + data.name + ".webp" + e)
@ -16,9 +18,4 @@ export const POST = (async ({ request }) => {
return new Response(JSON.stringify({msg: "Deleted image successfully"}),{ return new Response(JSON.stringify({msg: "Deleted image successfully"}),{
status: 200, status: 200,
}); });
}
else{
throw error(403, "Password incorrect")
}
}) satisfies RequestHandler; }) satisfies RequestHandler;

View File

@ -1,13 +1,16 @@
import path from 'path' import path from 'path'
import type { RequestHandler } from '@sveltejs/kit'; import type { RequestHandler } from '@sveltejs/kit';
import { BEARER_TOKEN } from '$env/static/private'
import { IMAGE_DIR } from '$env/static/private' import { IMAGE_DIR } from '$env/static/private'
import { rename } from 'node:fs'; import { rename } from 'node:fs';
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { authenticateUser } from '$lib/js/authenticate';
export const POST = (async ({ request, cookies }) => {
const data = await request.json();
const user = await authenticateUser(cookies)
if(!user) throw error(401, "need to be logged in")
if(!user.access.includes("rezepte")) throw error(401, "You don't have the required permission to do this")
export const POST = (async ({ request }) => {
const data = await request.json();
if(data.bearer === BEARER_TOKEN){
[ "full", "thumb", "placeholder"].forEach((folder) => { [ "full", "thumb", "placeholder"].forEach((folder) => {
const old_path = path.join(IMAGE_DIR, "rezepte", folder, data.old_name + ".webp") const old_path = path.join(IMAGE_DIR, "rezepte", folder, data.old_name + ".webp")
rename(old_path, path.join(IMAGE_DIR, "rezepte", folder, data.new_name + ".webp"), (e) => { rename(old_path, path.join(IMAGE_DIR, "rezepte", folder, data.new_name + ".webp"), (e) => {
@ -15,12 +18,8 @@ export const POST = (async ({ request }) => {
if(e) throw error(500, "could not mv: " + old_path) if(e) throw error(500, "could not mv: " + old_path)
}) })
}); });
return new Response(JSON.stringify({msg: "Deleted image successfully"}),{ return new Response(JSON.stringify({msg: "Deleted image successfully"}),{
status: 200, status: 200,
}); });
}
else{
throw error(403, "Password incorrect")
}
}) satisfies RequestHandler; }) satisfies RequestHandler;

View File

@ -0,0 +1,45 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import { sign } from 'jsonwebtoken';
import { verify} from 'argon2';
import { COOKIE_SECRET } from '$env/static/private'
import { PEPPER } from '$env/static/private'
import { dbConnect, dbDisconnect } from '../../../utils/db';
import { User } from '../../../models/User';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request}) => {
const {username, password} = await request.json()
await dbConnect()
let res = await User.findOne({username: username}, 'pass_hash salt').lean()
await dbDisconnect()
if(!res){
console.log("NOT FOUND")
throw error(401, {message: "wrong password or user does not exist"})
}
const stored_pw = res.pass_hash
const salt = res.salt
const isMatch = await verify(stored_pw, password + PEPPER, {salt})
if(!isMatch){
throw error(401, {message: "wrong password or user does not exist"})
}
res = await createJWT(username)
return new Response(JSON.stringify(res))
};
async function createJWT(username) {
const payload = {
username: username,
};
const masterSecret = COOKIE_SECRET;
const secretKey = masterSecret;
const jwt = sign(payload, secretKey);
console.log(jwt)
return jwt
}

View File

@ -0,0 +1,50 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import { hash } from 'argon2';
import { randomBytes } from 'crypto';
import { ALLOW_REGISTRATION } from '$env/static/private';
import { PEPPER } from '$env/static/private';
import { User } from '../../../models/User';
import { dbConnect, dbDisconnect } from '../../../utils/db';
// header: use for bearer token for now
// recipe json in body
export const POST: RequestHandler = async ({request}) => {
if(ALLOW_REGISTRATION){
const {username, password, access} = await request.json()
const salt = randomBytes(32).toString('hex'); // Generate a random salt
const pass_hash = await hashPassword(password + PEPPER, salt)
await dbConnect();
try{
await User.create({
username: username,
pass_hash: pass_hash,
salt: salt,
access: access,
})
}catch(e){
await dbDisconnect();
throw error(400, e);
}
await dbDisconnect();
return new Response(JSON.stringify({message: "User added successfully"}),
{status: 200}
);
}
else{
throw error(401, "user registration currently closed")
}
};
async function hashPassword(password, salt) {
try {
const hashedPassword = await hash(password, salt); // Hash the password with the salt and pepper
return hashedPassword;
} catch (error) {
console.error('Error hashing password:', error);
}
}

View File

@ -0,0 +1,62 @@
import type { RequestHandler } from '@sveltejs/kit';
import { error } from '@sveltejs/kit';
import { verify } from 'jsonwebtoken';
import { hash} from 'argon2';
import { randomBytes } from 'crypto';
import { COOKIE_SECRET } from '$env/static/private'
import { ALLOW_REGISTRATION } from '$env/static/private'
import { User } from '../../../models/User';
import { dbConnect, dbDisconnect } from '../../../utils/db';
import { getJWTFromRequest } from '../../../utils/cookie';
// header: use for bearer token for now
// recipe json in body
export const GET: RequestHandler = async ({request}) => {
const jwt = getJWTFromRequest(request)
// Set your master secret key (replace with your own secret)
const masterSecret = COOKIE_SECRET;
const secretKey = masterSecret
let decoded
try{
decoded = await verify(jwt, secretKey);
}
catch(e){
throw error(401, "Cookies have changed, please log in again")
}
await dbConnect()
let res = await User.findOne({username: decoded.username}, 'access').lean();
await dbDisconnect()
if(!res){
throw error(404, "User for this Cookie does no longer exist")
}
return new Response(JSON.stringify({
username: decoded.username,
access: res.access
}), {status: 200})
};
async function hashPassword(password, salt) {
try {
const hashedPassword = await hash(password, salt); // Hash the password with the salt
return hashedPassword;
} catch (error) {
console.error('Error hashing password:', error);
}
}
async function createJWT(username, userSalt) {
const payload = {
username: username,
};
const masterSecret = COOKIE_SECRET;
const secretKey = masterSecret + userSalt;
const jwt = sign(payload, secretKey);
return jwt
}