Compare commits

..

3 Commits

Author SHA1 Message Date
8ee07b57eb allow admin access without valid link 2025-09-07 19:20:42 +00:00
038f014f9c add renew function 2025-09-07 18:53:36 +00:00
17ff666965 improve setting page 2025-09-07 18:43:15 +00:00
7 changed files with 79 additions and 57 deletions

View File

@ -2,12 +2,9 @@ import sqlite3
from flask import render_template, request, session from flask import render_template, request, session
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
import geoip2.database import geoip2.database
from auth import require_secret
from collections import defaultdict from collections import defaultdict
import pandas as pd import pandas as pd
from typing import Optional, List, Tuple from typing import Optional, List, Tuple
import json
import os
import auth import auth
import helperfunctions as hf import helperfunctions as hf
@ -314,8 +311,6 @@ def songs_dashboard():
) )
@require_secret
def connections(): def connections():
title_short = app_config.get('TITLE_SHORT', 'Default Title') title_short = app_config.get('TITLE_SHORT', 'Default Title')
title_long = app_config.get('TITLE_LONG' , 'Default Title') title_long = app_config.get('TITLE_LONG' , 'Default Title')
@ -324,7 +319,7 @@ def connections():
title_short=title_short, title_short=title_short,
title_long=title_long) title_long=title_long)
@require_secret
def dashboard(): def dashboard():
if 'filetype' not in session: if 'filetype' not in session:
session['filetype'] = 'audio' session['filetype'] = 'audio'
@ -603,7 +598,6 @@ def dashboard():
) )
@require_secret
def file_access(): def file_access():
if 'timeframe' not in session: if 'timeframe' not in session:
session['timeframe'] = 'last24hours' session['timeframe'] = 'last24hours'

12
app.py
View File

@ -43,16 +43,16 @@ if os.environ.get('FLASK_ENV') == 'production':
app.config['SESSION_COOKIE_SAMESITE'] = 'None' app.config['SESSION_COOKIE_SAMESITE'] = 'None'
app.config['SESSION_COOKIE_SECURE'] = True app.config['SESSION_COOKIE_SECURE'] = True
app.add_url_rule('/dashboard', view_func=a.dashboard) app.add_url_rule('/dashboard', view_func=auth.require_admin(a.dashboard))
app.add_url_rule('/file_access', view_func=a.file_access) app.add_url_rule('/file_access', view_func=auth.require_admin(a.file_access))
app.add_url_rule('/connections', view_func=a.connections) app.add_url_rule('/connections', view_func=auth.require_admin(a.connections))
app.add_url_rule('/mylinks', view_func=auth.mylinks) app.add_url_rule('/mylinks', view_func=auth.require_secret(auth.mylinks))
app.add_url_rule('/songs_dashboard', view_func=auth.require_admin(a.songs_dashboard))
app.add_url_rule('/remove_secret', view_func=auth.remove_secret, methods=['POST']) app.add_url_rule('/remove_secret', view_func=auth.remove_secret, methods=['POST'])
app.add_url_rule('/remove_token', view_func=auth.remove_token, methods=['POST']) app.add_url_rule('/remove_token', view_func=auth.remove_token, methods=['POST'])
app.add_url_rule('/searchcommand', view_func=search.searchcommand, methods=['POST']) app.add_url_rule('/searchcommand', view_func=search.searchcommand, methods=['POST'])
app.add_url_rule('/songs_dashboard', view_func=a.songs_dashboard)
app.add_url_rule('/admin/folder_secret_config_editor', view_func=auth.require_admin(fsce.folder_secret_config_editor), methods=['GET', 'POST']) app.add_url_rule('/admin/folder_secret_config_editor', view_func=auth.require_admin(fsce.folder_secret_config_editor), methods=['GET', 'POST'])
app.add_url_rule('/admin/folder_secret_config_editor/data', view_func=auth.require_admin(auth.load_folder_config)) app.add_url_rule('/admin/folder_secret_config_editor/data', view_func=auth.require_admin(auth.load_folder_config))
app.add_url_rule('/admin/folder_secret_config_editor/action', view_func=auth.require_admin(fsce.folder_secret_config_action), methods=['POST']) app.add_url_rule('/admin/folder_secret_config_editor/action', view_func=auth.require_admin(fsce.folder_secret_config_action), methods=['POST'])

View File

@ -181,7 +181,6 @@ def require_secret(f):
def require_admin(f): def require_admin(f):
@wraps(f) @wraps(f)
@require_secret
def decorated_function(*args, **kwargs): def decorated_function(*args, **kwargs):
if is_admin(): if is_admin():
return f(*args, **kwargs) return f(*args, **kwargs)
@ -197,7 +196,7 @@ def save_folder_config(data):
json.dump(folder_config, file, indent=4) json.dump(folder_config, file, indent=4)
return folder_config return folder_config
@require_secret
def mylinks(): def mylinks():
scheme = request.scheme # current scheme (http or https) scheme = request.scheme # current scheme (http or https)
valid_secrets = session.get('valid_secrets', []) valid_secrets = session.get('valid_secrets', [])

View File

@ -1,8 +1,5 @@
from flask import Flask, request, jsonify, render_template from flask import Flask, request, jsonify, render_template
import json
import os
from datetime import datetime from datetime import datetime
import secrets
import string import string
import auth import auth
@ -12,7 +9,6 @@ app_config = auth.return_app_config()
ALPHABET = string.ascii_letters + string.digits ALPHABET = string.ascii_letters + string.digits
@auth.require_admin
def folder_secret_config_editor(): def folder_secret_config_editor():
title_short = app_config.get('TITLE_SHORT', 'Default Title') title_short = app_config.get('TITLE_SHORT', 'Default Title')
title_long = app_config.get('TITLE_LONG' , 'Default Title') title_long = app_config.get('TITLE_LONG' , 'Default Title')
@ -22,7 +18,7 @@ def folder_secret_config_editor():
title_short=title_short, title_short=title_short,
title_long=title_long) title_long=title_long)
@auth.require_admin
def folder_secret_config_action(): def folder_secret_config_action():
p = request.get_json() p = request.get_json()
data = auth.return_folder_config() data = auth.return_folder_config()

View File

@ -7,14 +7,6 @@
<title>{% block title %}Meine Links{% endblock %}</title> <title>{% block title %}Meine Links{% endblock %}</title>
<!-- Android Theme Color --> <!-- Android Theme Color -->
<meta name="theme-color" content="#000"> <meta name="theme-color" content="#000">

View File

@ -147,7 +147,7 @@
const remBtn = document.createElement('button'); const remBtn = document.createElement('button');
remBtn.className = 'btn btn-outline-danger'; remBtn.className = 'btn btn-outline-danger';
remBtn.type = 'button'; remBtn.type = 'button';
remBtn.textContent = 'Remove'; remBtn.textContent = 'entfernen';
remBtn.addEventListener('click', () => removeFolder(key, i)); remBtn.addEventListener('click', () => removeFolder(key, i));
nameGroup.appendChild(remBtn); nameGroup.appendChild(remBtn);
} }
@ -170,7 +170,7 @@
const addFld = document.createElement('button'); const addFld = document.createElement('button');
addFld.className = 'btn btn-sm btn-primary mb-2'; addFld.className = 'btn btn-sm btn-primary mb-2';
addFld.type = 'button'; addFld.type = 'button';
addFld.textContent = 'Add Folder'; addFld.textContent = 'Ordner hinzufügen';
addFld.addEventListener('click', () => addFolder(key)); addFld.addEventListener('click', () => addFolder(key));
body.appendChild(addFld); body.appendChild(addFld);
} }
@ -186,32 +186,49 @@
actions.appendChild(openButton); actions.appendChild(openButton);
} }
if (!isEdit) {
const openButton = document.createElement('button');
openButton.className = 'btn btn-secondary btn-sm me-2';
openButton.onclick = () => toClipboard(`${window.location.origin}/?secret=${rec.secret}`);
openButton.textContent = 'Link kopieren';
actions.appendChild(openButton);
}
const delBtn = document.createElement('button'); const delBtn = document.createElement('button');
delBtn.className = 'btn btn-danger btn-sm me-2 delete-btn'; delBtn.className = 'btn btn-danger btn-sm me-2 delete-btn';
delBtn.type = 'button'; delBtn.type = 'button';
delBtn.textContent = 'Delete'; delBtn.textContent = 'löschen';
delBtn.dataset.secret = key; delBtn.dataset.secret = key;
actions.appendChild(delBtn); actions.appendChild(delBtn);
const cloneBtn = document.createElement('button'); const cloneBtn = document.createElement('button');
cloneBtn.className = 'btn btn-secondary btn-sm me-2'; cloneBtn.className = 'btn btn-secondary btn-sm me-2';
cloneBtn.type = 'button'; cloneBtn.type = 'button';
cloneBtn.textContent = 'Clone'; cloneBtn.textContent = 'clonen';
cloneBtn.addEventListener('click', () => cloneRec(key)); cloneBtn.addEventListener('click', () => cloneRec(key));
actions.appendChild(cloneBtn); actions.appendChild(cloneBtn);
if (isEdit || expired) {
const renewBtn = document.createElement('button');
renewBtn.className = 'btn btn-info btn-sm me-2';
renewBtn.type = 'button';
renewBtn.textContent = 'erneuern';
renewBtn.addEventListener('click', () => renewRec(key));
actions.appendChild(renewBtn);
}
if (isEdit) { if (isEdit) {
const saveBtn = document.createElement('button'); const saveBtn = document.createElement('button');
saveBtn.className = 'btn btn-success btn-sm'; saveBtn.className = 'btn btn-success btn-sm';
saveBtn.type = 'button'; saveBtn.type = 'button';
saveBtn.textContent = 'Save'; saveBtn.textContent = 'speichern';
saveBtn.addEventListener('click', () => saveRec(key)); saveBtn.addEventListener('click', () => saveRec(key));
actions.appendChild(saveBtn); actions.appendChild(saveBtn);
} else { } else {
const editBtn = document.createElement('button'); const editBtn = document.createElement('button');
editBtn.className = 'btn btn-warning btn-sm'; editBtn.className = 'btn btn-warning btn-sm';
editBtn.type = 'button'; editBtn.type = 'button';
editBtn.textContent = 'Edit'; editBtn.textContent = 'bearbeiten';
editBtn.addEventListener('click', () => editRec(key)); editBtn.addEventListener('click', () => editRec(key));
actions.appendChild(editBtn); actions.appendChild(editBtn);
} }
@ -241,10 +258,49 @@
const rec = JSON.parse(JSON.stringify(data[idx])); const rec = JSON.parse(JSON.stringify(data[idx]));
const existing = data.map(r => r.secret); const existing = data.map(r => r.secret);
rec.secret = generateSecret(existing); rec.secret = generateSecret(existing);
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 35);
// Format as DD.MM.YYYY for validity input
const dd = String(futureDate.getDate()).padStart(2, '0');
const mm = String(futureDate.getMonth() + 1).padStart(2, '0');
const yyyy = futureDate.getFullYear();
rec.validity = `${dd}.${mm}.${yyyy}`;
data.splice(idx+1, 0, rec); data.splice(idx+1, 0, rec);
editing.add(rec.secret); editing.add(rec.secret);
render(); render();
} }
async function renewRec(secret) {
// find current record
const rec = data.find(r => r.secret === secret);
if (!rec) return;
// generate a fresh unique secret
const existing = data.map(r => r.secret);
const newSecret = generateSecret(existing);
// validity = today + 35 days, formatted as YYYY-MM-DD
const future = new Date();
future.setDate(future.getDate() + 35);
const yyyy = future.getFullYear();
const mm = String(future.getMonth() + 1).padStart(2, '0');
const dd = String(future.getDate()).padStart(2, '0');
const validity = `${yyyy}-${mm}-${dd}`;
// keep folders unchanged
const folders = rec.folders.map(f => ({
foldername: f.foldername,
folderpath: f.folderpath
}));
// persist via existing endpoint
await sendAction({
action: 'update',
oldSecret: secret,
newSecret,
validity,
folders
});
}
function addFolder(secret) { function addFolder(secret) {
data.find(r => r.secret === secret).folders.push({foldername:'', folderpath:''}); data.find(r => r.secret === secret).folders.push({foldername:'', folderpath:''});
render(); render();

View File

@ -1,27 +1,12 @@
<!doctype html> {# templates/file_access.html #}
<html> {% extends 'base.html' %}
<head>
<meta charset="utf-8">
<meta property="og:title" content="{{ title_long }}" /> {# page title #}
<meta property="og:description" content="... uns aber, die wir gerettet werden, ist es eine Gotteskraft." /> {% block title %}Dateizugriffe{% endblock %}
<meta property="og:image" content="/icon/logo-300x300.png" />
<title>{{ title_long }}</title> {# page content #}
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> {% block content %}
<link rel="stylesheet" href="{{ url_for('static', filename='theme.css') }}">
<link rel="stylesheet" href="{{ url_for('static', filename='app.css') }}">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
</head>
<body>
<header class="site-header">
<img src="/custom_logo/logoW.png" alt="Logo" class="logo">
<h1>{{ title_long }}</h1>
</header>
<div class="container"> <div class="container">
<div class="alert alert-warning">Du hast keine Links die noch gültig sind.<br>Bitte den Freigabelink erneut anklicken.</div> <div class="alert alert-warning">Du hast keine gültige Freigaben.<br>Bitte Ordner mit einem Freigabelink freischalten.</div>
</div> </div>
</body> {% endblock %}
</html>