Compare commits

...

16 Commits

Author SHA1 Message Date
janis 301cbf9d06 pentest_report.html gelöscht
Deploy / security_scan (push) Successful in 21s
Deploy / code_scan (push) Successful in 21s
Deploy / deploy (push) Successful in 36s
2026-05-28 10:58:56 +00:00
janis d1025134cd .gitea/workflows/delpoy.yml aktualisiert
Deploy / security_scan (push) Successful in 9s
Deploy / code_scan (push) Successful in 20s
Deploy / deploy (push) Successful in 2m5s
2026-05-20 18:03:25 +00:00
janis 8766e534df .gitea/workflows/delpoy.yml aktualisiert
Deploy / security_scan (push) Failing after 10s
Deploy / code_scan (push) Successful in 20s
Deploy / deploy (push) Has been skipped
2026-05-20 17:56:25 +00:00
janis 31e480d3de trying docker hardened image
Deploy / security_scan (push) Failing after 7s
Deploy / code_scan (push) Successful in 19s
Deploy / deploy (push) Has been skipped
2026-05-20 19:48:56 +02:00
janis c773169ff6 revert
Deploy / security_scan (push) Failing after 10s
Deploy / code_scan (push) Successful in 19s
Deploy / deploy (push) Has been skipped
2026-05-20 19:41:58 +02:00
janis f8560068dd moving away from mariadb
Deploy / security_scan (push) Failing after 20s
Deploy / code_scan (push) Successful in 20s
Deploy / deploy (push) Has been skipped
2026-05-20 19:39:16 +02:00
janis ccae7bf73c Merge branch 'main' of https://git.jakach.ch/jakach/jakach-login
Deploy / security_scan (push) Failing after 9s
Deploy / code_scan (push) Successful in 20s
Deploy / deploy (push) Has been skipped
2026-05-20 19:35:34 +02:00
janis fc3181ee3b fixing minor security issues 2026-05-20 19:35:11 +02:00
janis acf09db63e docker-compose.yml aktualisiert
Deploy / security_scan (push) Failing after 16s
Deploy / code_scan (push) Failing after 18s
Deploy / deploy (push) Has been skipped
2026-05-20 17:31:02 +00:00
janis 7a7e0bd185 .gitea/workflows/delpoy.yml aktualisiert
Deploy / security_scan (push) Failing after 19s
Deploy / code_scan (push) Failing after 19s
Deploy / deploy (push) Has been skipped
2026-05-20 17:27:09 +00:00
janis 11d26dfe8b switching github.com to git.jakach.ch
Deploy / deploy (push) Successful in 14s
2026-05-16 08:55:05 +00:00
janis a540a57efc fixing a small thing wher eusers could update theyr username to other users usernames
Deploy / deploy (push) Successful in 24s
2026-05-15 10:20:47 +02:00
janis 37cf88a06e fixing potentiall xss in external domains list
Deploy / deploy (push) Successful in 28s
2026-05-15 10:13:23 +02:00
janis eb3ffed163 adding http security headers 2026-05-15 10:08:08 +02:00
janis 091d00b5c2 fixing version leakage 2026-05-15 10:06:55 +02:00
janis 10fb66c470 set coockies to secure 2026-05-15 09:59:51 +02:00
14 changed files with 693 additions and 528 deletions
+448 -18
View File
@@ -2,15 +2,268 @@ name: Deploy
on:
push:
branches: [main]
branches:
- main
env:
GIT_HOST: git.jakach.ch
GIT_REPO: jakach/jakach-login
GIT_BRANCH: main
APP_NAME: auth
APP_DOMAIN: auth.jakach.ch
APP_PORT: 447
SECURITY_SCAN_ENABLED: ${{ vars.SECURITY_SCAN_ENABLED }}
CODE_SCAN_ENABLED: ${{ vars.CODE_SCAN_ENABLED }}
TRIVY_SEVERITY: HIGH,CRITICAL
TRIVY_IMAGE_SCANNERS: vuln
TRIVY_VEX: repo
TRIVY_FS_SCANNERS: vuln,misconfig,secret
SEMGREP_CONFIG: p/default
jobs:
security_scan:
runs-on: ubuntu-latest
env:
GIT_USER: ${{ vars.GIT_USER }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
TRIVY_REGISTRY_USER: ${{ vars.TRIVY_REGISTRY_USER }}
TRIVY_REGISTRY_PASSWORD: ${{ secrets.TRIVY_REGISTRY_PASSWORD }}
steps:
- name: Scan Docker images for vulnerabilities
run: |
set -Eeuo pipefail
case "${SECURITY_SCAN_ENABLED:-true}" in
false|False|FALSE|0|no|No|NO|off|Off|OFF)
echo "Security scan disabled by SECURITY_SCAN_ENABLED=${SECURITY_SCAN_ENABLED}"
exit 0
;;
esac
: "${GIT_USER:?GIT_USER is required}"
: "${GIT_TOKEN:?GIT_TOKEN is required}"
if command -v apk >/dev/null 2>&1; then
apk add --no-cache ca-certificates curl git
elif command -v apt-get >/dev/null 2>&1; then
apt-get update
apt-get install -y ca-certificates curl git
elif command -v dnf >/dev/null 2>&1; then
dnf install -y ca-certificates curl git
elif command -v yum >/dev/null 2>&1; then
yum install -y ca-certificates curl git
else
echo "Unsupported package manager"
exit 1
fi
if ! command -v trivy >/dev/null 2>&1; then
mkdir -p "$HOME/.local/bin"
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
| sh -s -- -b "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"
fi
REPO_URL="https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_REPO}"
git clone \
--branch "$GIT_BRANCH" \
"$REPO_URL" \
source
cd source
COMPOSE_FILE=""
for file in docker-compose.yml compose.yml compose.yaml; do
if [ -f "$file" ]; then
COMPOSE_FILE="$file"
break
fi
done
if [ -z "$COMPOSE_FILE" ]; then
echo "No docker compose file found"
exit 0
fi
if docker compose version >/dev/null 2>&1; then
docker compose -f "$COMPOSE_FILE" config --images > images.txt
else
awk '
/^[[:space:]]*image:[[:space:]]*/ {
sub(/^[[:space:]]*image:[[:space:]]*/, "")
gsub(/["\047]/, "")
print
}
' "$COMPOSE_FILE" > images.txt
fi
sort -u images.txt -o images.txt
if [ ! -s images.txt ]; then
echo "No Docker images found to scan"
exit 0
fi
if [ -n "${TRIVY_REGISTRY_USER:-}" ] || [ -n "${TRIVY_REGISTRY_PASSWORD:-}" ]; then
: "${TRIVY_REGISTRY_USER:?TRIVY_REGISTRY_USER is required when TRIVY_REGISTRY_PASSWORD is set}"
: "${TRIVY_REGISTRY_PASSWORD:?TRIVY_REGISTRY_PASSWORD is required when TRIVY_REGISTRY_USER is set}"
echo "Using registry credentials from TRIVY_REGISTRY_USER/TRIVY_REGISTRY_PASSWORD"
fi
TRIVY_IGNORE_ARGS=""
if [ -f cve_blacklist.txt ]; then
awk '
/^[[:space:]]*($|#)/ {
next
}
{
print $1
}
' cve_blacklist.txt > .trivyignore
if [ -s .trivyignore ]; then
TRIVY_IGNORE_ARGS="--ignorefile .trivyignore"
echo "Using CVE blacklist from cve_blacklist.txt"
fi
fi
echo "Scanning Docker images for ${TRIVY_SEVERITY} vulnerabilities:"
cat images.txt
failed=0
while IFS= read -r image; do
[ -n "$image" ] || continue
echo ""
echo "Scanning ${image}"
if [ -n "${TRIVY_REGISTRY_USER:-}" ]; then
if ! trivy \
image \
--username "${TRIVY_REGISTRY_USER}" \
--password "${TRIVY_REGISTRY_PASSWORD}" \
--scanners "${TRIVY_IMAGE_SCANNERS}" \
--vex "${TRIVY_VEX}" \
--exit-code 1 \
--severity "${TRIVY_SEVERITY}" \
--ignore-unfixed \
${TRIVY_IGNORE_ARGS} \
--no-progress \
"${image}"; then
failed=1
fi
elif ! trivy \
image \
--scanners "${TRIVY_IMAGE_SCANNERS}" \
--vex "${TRIVY_VEX}" \
--exit-code 1 \
--severity "${TRIVY_SEVERITY}" \
--ignore-unfixed \
${TRIVY_IGNORE_ARGS} \
--no-progress \
"${image}"; then
failed=1
fi
done < images.txt
if [ "$failed" -ne 0 ]; then
echo "WARNING: One or more Docker image scans failed or found ${TRIVY_SEVERITY} vulnerabilities. Deployment stopped."
exit 1
fi
echo "No high or critical vulnerabilities found"
code_scan:
runs-on: ubuntu-latest
env:
GIT_USER: ${{ vars.GIT_USER }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
steps:
- name: Scan source code
run: |
set -Eeuo pipefail
case "${CODE_SCAN_ENABLED:-true}" in
false|False|FALSE|0|no|No|NO|off|Off|OFF)
echo "Code scan disabled by CODE_SCAN_ENABLED=${CODE_SCAN_ENABLED}"
exit 0
;;
esac
: "${GIT_USER:?GIT_USER is required}"
: "${GIT_TOKEN:?GIT_TOKEN is required}"
if command -v apk >/dev/null 2>&1; then
apk add --no-cache ca-certificates curl git python3 py3-pip
elif command -v apt-get >/dev/null 2>&1; then
apt-get update
apt-get install -y ca-certificates curl git python3 python3-pip python3-venv
elif command -v dnf >/dev/null 2>&1; then
dnf install -y ca-certificates curl git python3 python3-pip
elif command -v yum >/dev/null 2>&1; then
yum install -y ca-certificates curl git python3 python3-pip
else
echo "Unsupported package manager"
exit 1
fi
if ! command -v semgrep >/dev/null 2>&1; then
python3 -m venv "$HOME/.semgrep-venv"
. "$HOME/.semgrep-venv/bin/activate"
python3 -m pip install --upgrade pip
python3 -m pip install semgrep
fi
if ! command -v trivy >/dev/null 2>&1; then
mkdir -p "$HOME/.local/bin"
curl -sfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh \
| sh -s -- -b "$HOME/.local/bin"
export PATH="$HOME/.local/bin:$PATH"
fi
REPO_URL="https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_REPO}"
git clone \
--branch "$GIT_BRANCH" \
"$REPO_URL" \
source
cd source
semgrep scan \
--config "${SEMGREP_CONFIG}" \
--error \
--metrics=off
trivy fs \
--scanners "${TRIVY_FS_SCANNERS}" \
--exit-code 1 \
--severity "${TRIVY_SEVERITY}" \
--ignore-unfixed \
--no-progress \
.
deploy:
runs-on: ubuntu-latest
needs:
- security_scan
- code_scan
steps:
- name: Install SSH client
- name: Install dependencies
run: |
set -e
if command -v apk >/dev/null 2>&1; then
apk add --no-cache openssh-client git bash
elif command -v apt-get >/dev/null 2>&1; then
@@ -21,55 +274,232 @@ jobs:
elif command -v yum >/dev/null 2>&1; then
yum install -y openssh-clients git bash
else
echo "No supported package manager found"
echo "Unsupported package manager"
exit 1
fi
- name: Run deploy
- name: Deploy application
env:
SSH_KEY: ${{ secrets.SSH_KEY }}
SSH_USER: ${{ vars.SSH_USER }}
SSH_IP: ${{ vars.SSH_IP }}
GIT_USER: ${{ vars.GIT_USER }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
APP_DIR: /home/deploy/my-app
GIT_REPO: Jakach/my-app.git
GIT_BRANCH: main
run: |
cat > deploy.sh <<'EOF'
cat > deploy.sh <<'OUTER_EOF'
#!/usr/bin/env bash
set -Eeuo pipefail
: "${SSH_KEY:?SSH_KEY is required}"
: "${SSH_USER:?SSH_USER is required}"
: "${SSH_IP:?SSH_IP is required}"
: "${GIT_USER:?GIT_USER is required}"
: "${GIT_TOKEN:?GIT_TOKEN is required}"
APP_DIR="/srv/systems/jakach-login"
GIT_HOST="${GIT_HOST:-git.jakach.ch}"
GIT_REPO="jakach/jakach-login.git"
GIT_BRANCH="${GIT_BRANCH:-main}"
REPO_NAME="$(basename "$GIT_REPO")"
APP_DIR="/srv/systems/${REPO_NAME}"
mkdir -p ~/.ssh
chmod 700 ~/.ssh
printf '%s\n' "$SSH_KEY" | tr -d '\r' > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "$SSH_IP" >> ~/.ssh/known_hosts 2>/dev/null || true
ssh -i ~/.ssh/deploy_key \
ssh \
-i ~/.ssh/deploy_key \
-o StrictHostKeyChecking=yes \
-o IdentitiesOnly=yes \
"$SSH_USER@$SSH_IP" \
"export APP_DIR='$APP_DIR' GIT_HOST='$GIT_HOST' GIT_REPO='$GIT_REPO' GIT_BRANCH='$GIT_BRANCH' GIT_USER='$GIT_USER' GIT_TOKEN='$GIT_TOKEN'; bash -s" <<'REMOTE'
"
export \
APP_NAME='${APP_NAME}' \
APP_DOMAIN='${APP_DOMAIN}' \
APP_PORT='${APP_PORT}' \
APP_DIR='${APP_DIR}' \
GIT_HOST='${GIT_HOST}' \
GIT_REPO='${GIT_REPO}' \
GIT_BRANCH='${GIT_BRANCH}' \
GIT_USER='${GIT_USER}' \
GIT_TOKEN='${GIT_TOKEN}';
bash -s
" <<'REMOTE_EOF'
set -Eeuo pipefail
REPO_URL="https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_REPO}"
PROXY_DIR="/srv/systems/proxy"
NGINX_CONF="${PROXY_DIR}/nginx_conf/nginx.conf"
GET_CERT_SCRIPT="${PROXY_DIR}/get_cert.sh"
RENEW_SCRIPT="${PROXY_DIR}/renew_cert.sh"
FIRST_DEPLOY=0
PROXY_RESTART_REQUIRED=0
# --------------------------------------------------
# Clone repository if missing
# --------------------------------------------------
if [ ! -d "$APP_DIR/.git" ]; then
echo "Repository missing, cloning..."
FIRST_DEPLOY=1
mkdir -p "$(dirname "$APP_DIR")"
git clone \
--branch "$GIT_BRANCH" \
"$REPO_URL" \
"$APP_DIR"
fi
# --------------------------------------------------
# Update repository
# --------------------------------------------------
cd "$APP_DIR"
git remote set-url origin "https://${GIT_USER}:${GIT_TOKEN}@${GIT_HOST}/${GIT_REPO}"
git remote set-url origin "$REPO_URL"
git fetch origin "$GIT_BRANCH"
git checkout "$GIT_BRANCH"
git pull origin "$GIT_BRANCH"
docker compose down
# --------------------------------------------------
# Create nginx reverse proxy entry
# --------------------------------------------------
if [ "$FIRST_DEPLOY" -eq 1 ]; then
if ! grep -q "proxy_pass http://192.168.1.109:${APP_PORT}/;" "$NGINX_CONF"; then
echo "Creating nginx entry..."
PROXY_RESTART_REQUIRED=1
cat >> "$NGINX_CONF" <<NGINXEOF
# ---------------------
# ${APP_NAME} Service
# ---------------------
server {
server_tokens off;
listen 443 ssl;
server_name ${APP_DOMAIN};
ssl_certificate /etc/nginx/certs/${APP_NAME}.fullchain.pem;
ssl_certificate_key /etc/nginx/certs/${APP_NAME}.privkey.pem;
if (\$allowed_country = no) {
return 403;
}
location / {
proxy_pass http://192.168.1.109:${APP_PORT}/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
}
}
NGINXEOF
else
echo "Nginx entry already exists"
fi
# --------------------------------------------------
# Add domain to get_cert.sh
# --------------------------------------------------
if ! grep -q "\"${APP_DOMAIN}\"" "$GET_CERT_SCRIPT"; then
echo "Adding domain to get_cert.sh"
sed -i "/DOMAINS=(/a\ \"${APP_DOMAIN}\"" "$GET_CERT_SCRIPT"
PROXY_RESTART_REQUIRED=1
fi
# --------------------------------------------------
# Create certificate if missing
# --------------------------------------------------
if [ ! -d "/etc/letsencrypt/live/${APP_DOMAIN}" ]; then
echo "Creating certificate..."
sudo certbot certonly \
--standalone \
--non-interactive \
--agree-tos \
-m admin@jakach.ch \
-d "${APP_DOMAIN}"
PROXY_RESTART_REQUIRED=1
else
echo "Certificate already exists"
fi
# --------------------------------------------------
# Update renew_cert.sh
# --------------------------------------------------
if ! grep -q "${APP_DOMAIN}/privkey.pem" "$RENEW_SCRIPT"; then
echo "Updating renew_cert.sh"
cat >> "$RENEW_SCRIPT" <<CERTEOF
cp /etc/letsencrypt/live/${APP_DOMAIN}/privkey.pem certs/${APP_NAME}.privkey.pem
cp /etc/letsencrypt/live/${APP_DOMAIN}/fullchain.pem certs/${APP_NAME}.fullchain.pem
CERTEOF
PROXY_RESTART_REQUIRED=1
fi
chmod +x "$RENEW_SCRIPT"
# --------------------------------------------------
# Make renew non-interactive
# --------------------------------------------------
sed -i 's/certbot renew$/certbot renew -n/' "$RENEW_SCRIPT" || true
# --------------------------------------------------
# Renew certs + restart proxy
# --------------------------------------------------
if [ "$PROXY_RESTART_REQUIRED" -eq 1 ]; then
cd "$PROXY_DIR"
sudo bash /srv/systems/proxy/renew_cert.sh
docker compose down || true
docker compose up -d --build
REMOTE
EOF
else
echo "Proxy already configured, skipping certificate renewal and proxy restart"
fi
else
echo "Existing deployment, skipping proxy setup, certificate renewal and proxy restart"
fi
# --------------------------------------------------
# Deploy app
# --------------------------------------------------
cd "$APP_DIR"
if [ -f docker-compose.yml ] || [ -f compose.yml ] || [ -f compose.yaml ]; then
echo "Deploying docker stack..."
docker compose down || true
docker compose up -d --build
else
echo "No docker compose file found"
fi
echo "Deployment complete"
REMOTE_EOF
OUTER_EOF
chmod +x deploy.sh
./deploy.sh
+6 -7
View File
@@ -14,19 +14,18 @@ Using Jakach Login is straightforward:
1. **Clone the repository:**
```bash
git clone https://github.com/jakani24/jakach-login
git clone https://git.jakach.ch/jakani24/jakach-login
```
2. **Create the `certs/` folder and set up SSL certificates:**
```bash
mkdir certs/
```
- Generate certificates (e.g., using [Let's Encrypt](https://letsencrypt.org/getting-started/#with-shell-access)).
3. **Create a Docker volume for database storage:**
```bash
docker volume create jakach-login-db-storage
```
4. **Run the system using Docker Compose:**
4. **Authenticate to Docker Hardened Images:**
```bash
docker login dhi.io
```
5. **Run the system using Docker Compose:**
```bash
docker-compose up
```
+11
View File
@@ -1,7 +1,18 @@
ServerTokens Prod
ServerSignature Off
TraceEnable Off
<VirtualHost *:80>
ServerName auth.jakach.ch
DocumentRoot /var/www/html
Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains"
Header always set X-Content-Type-Options "nosniff"
Header always set X-Frame-Options "DENY"
Header always set Referrer-Policy "strict-origin-when-cross-origin"
Header always set Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), publickey-credentials-get=(self)"
Header always set Content-Security-Policy "base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests"
<Directory /var/www/html>
Options FollowSymLinks
AllowOverride All
+48 -6
View File
@@ -818,8 +818,25 @@ function updatePasswordStrength() {
data.domains.forEach(d => {
const item = document.createElement('div');
item.className = 'list-group-item d-flex justify-content-between align-items-center';
item.innerHTML = '<span><strong>' + d.domain + '</strong><br><small class="text-muted">Approved: ' + d.confirmed_at + '</small></span>' +
'<button class="btn btn-sm btn-outline-danger" onclick="removeDomain(' + d.id + ')">Revoke</button>';
const details = document.createElement('span');
const domain = document.createElement('strong');
domain.textContent = d.domain;
const approvedAt = document.createElement('small');
approvedAt.className = 'text-muted';
approvedAt.textContent = 'Approved: ' + d.confirmed_at;
details.appendChild(domain);
details.appendChild(document.createElement('br'));
details.appendChild(approvedAt);
const revokeButton = document.createElement('button');
revokeButton.type = 'button';
revokeButton.className = 'btn btn-sm btn-outline-danger';
revokeButton.textContent = 'Revoke';
revokeButton.addEventListener('click', () => removeDomain(Number(d.id)));
item.appendChild(details);
item.appendChild(revokeButton);
list.appendChild(item);
});
});
@@ -881,9 +898,30 @@ function updatePasswordStrength() {
'sessions_revoked': 'Sessions revoked',
};
const label = actionLabels[e.action] || e.action;
item.innerHTML = '<div class="d-flex w-100 justify-content-between"><strong>' + label + '</strong><small class="text-muted">' + e.created_at + '</small></div>' +
'<small class="text-muted">' + (e.ip ? e.ip + ' &middot; ' : '') + (e.user_agent ? e.user_agent.substring(0, 60) + '...' : '') + '</small>' +
(e.details ? '<br><small>' + e.details + '</small>' : '');
const header = document.createElement('div');
header.className = 'd-flex w-100 justify-content-between';
const action = document.createElement('strong');
action.textContent = label;
const createdAt = document.createElement('small');
createdAt.className = 'text-muted';
createdAt.textContent = e.created_at;
header.appendChild(action);
header.appendChild(createdAt);
const metadata = document.createElement('small');
metadata.className = 'text-muted';
metadata.textContent = (e.ip ? e.ip + ' - ' : '') + (e.user_agent ? e.user_agent.substring(0, 60) + '...' : '');
item.appendChild(header);
item.appendChild(metadata);
if (e.details) {
const details = document.createElement('small');
details.textContent = e.details;
item.appendChild(document.createElement('br'));
item.appendChild(details);
}
list.appendChild(item);
});
});
@@ -904,7 +942,11 @@ function updatePasswordStrength() {
data.sessions.forEach(s => {
const item = document.createElement('div');
item.className = 'list-group-item d-flex justify-content-between align-items-center';
item.innerHTML = '<span><strong>' + (s.user_agent || 'Unknown device') + '</strong></span>';
const device = document.createElement('span');
const deviceName = document.createElement('strong');
deviceName.textContent = s.user_agent || 'Unknown device';
device.appendChild(deviceName);
item.appendChild(device);
list.appendChild(item);
});
});
+6
View File
@@ -19,6 +19,12 @@ if ($method === 'GET') {
$result = mysqli_stmt_get_result($stmt);
$domains = [];
while ($row = mysqli_fetch_assoc($result)) {
$domain = normalize_redirect_host($row['domain'] ?? '');
if ($domain === null) {
continue;
}
$row['domain'] = $domain;
$row['id'] = (int) $row['id'];
$domains[] = $row;
}
mysqli_stmt_close($stmt);
+7 -2
View File
@@ -10,6 +10,11 @@ secure_session_start();
require_same_origin_request();
require_csrf_token();
function print_json_response($data): void
{
print(htmlentities(json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT), ENT_NOQUOTES, 'UTF-8'));
}
// Assuming you've already established a database connection here
include "../../config/config.php";
$conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD,$DB_DATABASE);
@@ -104,7 +109,7 @@ try {
$createArgs = $WebAuthn->getCreateArgs(\hex2bin($userId), $userName, $userDisplayName, 60*4, $requireResidentKey, $userVerification, $crossPlatformAttachment);
header('Content-Type: application/json');
print(json_encode($createArgs));
print_json_response($createArgs);
// save challange to session. you have to deliver it to processGet later.
$_SESSION['challenge'] = $WebAuthn->getChallenge();
@@ -138,7 +143,7 @@ try {
$getArgs = $WebAuthn->getGetArgs($ids, 60*4, $typeUsb, $typeNfc, $typeBle, $typeHyb, $typeInt, $userVerification);
header('Content-Type: application/json');
print(json_encode($getArgs));
print_json_response($getArgs);
// save challange to session. you have to deliver it to processGet later.
$_SESSION['challenge'] = $WebAuthn->getChallenge();
+3 -4
View File
@@ -43,7 +43,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
}
// Sanitize and validate the input
$name = preg_replace("/[^a-zA-Z0-9_]/", "", $data['name']); // Allow only letters, numbers, and underscores
$name = strtolower(preg_replace("/[^a-zA-Z0-9_]/", "", $data['name']));
$email = trim((string) $data['email']);
if ($email !== "" && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo json_encode([
@@ -52,7 +52,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
]);
exit();
}
$telegram_id = htmlspecialchars($data['telegram_id'], ENT_QUOTES, 'UTF-8'); // Escape special characters
$telegram_id = htmlspecialchars($data['telegram_id'], ENT_QUOTES, 'UTF-8');
//check if username is allready taken
$id_check=0;
@@ -63,8 +63,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
mysqli_stmt_store_result($stmt);
mysqli_stmt_bind_result($stmt, $id_check);
mysqli_stmt_fetch($stmt);
if(mysqli_stmt_num_rows($stmt) > 0 && $username!==$name){
//this username is allready taken
if((mysqli_stmt_num_rows($stmt) > 0 && $username!==$name) || $name === ""){
echo json_encode([
'success' => false,
'message' => 'Username allready taken. Please choose another username.'
+8 -2
View File
@@ -7,6 +7,12 @@ include "../utils/security.php";
secure_session_start();
require_same_origin_request();
require_csrf_token();
function print_json_response($data): void
{
print(htmlentities(json_encode($data, JSON_HEX_TAG | JSON_HEX_AMP | JSON_HEX_APOS | JSON_HEX_QUOT), ENT_NOQUOTES, 'UTF-8'));
}
include "../../config/config.php";
$conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD,$DB_DATABASE);
if ($conn->connect_error) {
@@ -90,7 +96,7 @@ try {
// Get create arguments
$createArgs = $WebAuthn->getCreateArgs(\hex2bin($userId), $userName, $userDisplayName, 60*4, $requireResidentKey, $userVerification);
header('Content-Type: application/json');
print(json_encode($createArgs));
print_json_response($createArgs);
// Save challenge to session or somewhere else if needed
} else if ($fn === 'getGetArgs') {
@@ -120,7 +126,7 @@ try {
$getArgs = $WebAuthn->getGetArgs($ids, 60*4, $typeUsb, $typeNfc, $typeBle, $typeHyb, $typeInt, $userVerification);
header('Content-Type: application/json');
print(json_encode($getArgs));
print_json_response($getArgs);
// save challange to session. you have to deliver it to processGet later.
$_SESSION['challenge'] = $WebAuthn->getChallenge();
@@ -8,11 +8,11 @@ if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
exit;
}
$input = json_decode(file_get_contents('php://input'), true);
$domain = $input['domain'] ?? '';
$send_to = normalize_redirect_target($_SESSION["end_url"] ?? "/account/");
$domain = is_external_domain($send_to);
if ($domain === '' || !isset($_SESSION['id'])) {
echo json_encode(['success' => false, 'message' => 'Missing domain or not logged in.']);
if ($domain === null || !isset($_SESSION['id'])) {
echo json_encode(['success' => false, 'message' => 'Missing external domain or not logged in.']);
exit;
}
+85 -453
View File
@@ -35,7 +35,6 @@ if (!$user_found) {
}
//send telegram message
$device = $_SERVER['HTTP_USER_AGENT'] ?? "";
//$ip=$_SERVER["REMOTE_ADDR"];
$forwarded_for = $_SERVER["HTTP_X_FORWARDED_FOR"] ?? $_SERVER["REMOTE_ADDR"] ?? "";
$ip=trim(explode(",",$forwarded_for)[0]);
$location=get_location_from_ip($ip);
@@ -44,15 +43,20 @@ $token=bin2hex(random_bytes(128));
$token_hash=auth_token_hash($token);
$link="https://auth.jakach.ch/login/reset_pw.php?token=$token";
$tg_device = str_replace(['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\\_', '\\*', '\\[', '\\]', '\\(', '\\)', '\\~', '\\`', '\\>', '\\#', '\\+', '\\-', '\\=', '\\|', '\\{', '\\}', '\\.', '\\!'], $device);
$tg_username = str_replace(['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\\_', '\\*', '\\[', '\\]', '\\(', '\\)', '\\~', '\\`', '\\>', '\\#', '\\+', '\\-', '\\=', '\\|', '\\{', '\\}', '\\.', '\\!'], $_SESSION["username"]);
$tg_ip = str_replace(['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\\_', '\\*', '\\[', '\\]', '\\(', '\\)', '\\~', '\\`', '\\>', '\\#', '\\+', '\\-', '\\=', '\\|', '\\{', '\\}', '\\.', '\\!'], $ip);
$tg_location = str_replace(['_', '*', '[', ']', '(', ')', '~', '`', '>', '#', '+', '-', '=', '|', '{', '}', '.', '!'], ['\\_', '\\*', '\\[', '\\]', '\\(', '\\)', '\\~', '\\`', '\\>', '\\#', '\\+', '\\-', '\\=', '\\|', '\\{', '\\}', '\\.', '\\!'], ($location["country"] ?? "").", ".($location["state"] ?? "").", ".($location["city"] ?? ""));
$message = "*Password reset token*\n\n"
. "You have requested the reset of your password here is your reset link.\n\n"
. "*Link*: [click here]($link)\n\n"
. "*Details of this request:*\n"
. "• *Date&Time*: $date\n"
. "• *Device&Browser*: $device\n"
. "*Location*: ".$location["country"].", ".$location["state"].", ".$location["city"]."\n"
. "• *Account*: ".$_SESSION["username"]."\n"
. "• *IP*: $ip\n\n"
. "• *Device&Browser*: $tg_device\n"
. "*Location*: $tg_location\n"
. "• *Account*: $tg_username\n"
. "• *IP*: $tg_ip\n\n"
."If this was you, you can reset your password. If this was not you somebody else tried to reset your password!\n"
. "*Thank you for using Jakach login!*";
@@ -78,6 +82,12 @@ curl_close($ch);
//send mail
if(!empty($mail)){
$loc=$location["country"].", ".$location["state"].", ".$location["city"];
$html_username = htmlspecialchars($username, ENT_QUOTES, 'UTF-8');
$html_device = htmlspecialchars($device, ENT_QUOTES, 'UTF-8');
$html_ip = htmlspecialchars($ip, ENT_QUOTES, 'UTF-8');
$html_loc = htmlspecialchars($loc, ENT_QUOTES, 'UTF-8');
$html_mail = htmlspecialchars($mail, ENT_QUOTES, 'UTF-8');
$html_link = htmlspecialchars($link, ENT_QUOTES, 'UTF-8');
$content = '
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
@@ -89,433 +99,67 @@ if(!empty($mail)){
<meta name="supported-color-schemes" content="light dark" />
<title></title>
<style type="text/css" rel="stylesheet" media="all">
/* Base ------------------------------ */
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
body {
width: 100% !important;
height: 100%;
margin: 0;
-webkit-text-size-adjust: none;
}
a {
color: #3869D4;
}
a img {
border: none;
}
td {
word-break: break-word;
}
.preheader {
display: none !important;
visibility: hidden;
mso-hide: all;
font-size: 1px;
line-height: 1px;
max-height: 0;
max-width: 0;
opacity: 0;
overflow: hidden;
}
/* Type ------------------------------ */
body,
td,
th {
font-family: "Nunito Sans", Helvetica, Arial, sans-serif;
}
h1 {
margin-top: 0;
color: #333333;
font-size: 22px;
font-weight: bold;
text-align: left;
}
h2 {
margin-top: 0;
color: #333333;
font-size: 16px;
font-weight: bold;
text-align: left;
}
h3 {
margin-top: 0;
color: #333333;
font-size: 14px;
font-weight: bold;
text-align: left;
}
td,
th {
font-size: 16px;
}
p,
ul,
ol,
blockquote {
margin: .4em 0 1.1875em;
font-size: 16px;
line-height: 1.625;
}
p.sub {
font-size: 13px;
}
/* Utilities ------------------------------ */
.align-right {
text-align: right;
}
.align-left {
text-align: left;
}
.align-center {
text-align: center;
}
.u-margin-bottom-none {
margin-bottom: 0;
}
/* Buttons ------------------------------ */
.button {
background-color: #3869D4;
border-top: 10px solid #3869D4;
border-right: 18px solid #3869D4;
border-bottom: 10px solid #3869D4;
border-left: 18px solid #3869D4;
display: inline-block;
color: #FFF;
text-decoration: none;
border-radius: 3px;
box-shadow: 0 2px 3px rgba(0, 0, 0, 0.16);
-webkit-text-size-adjust: none;
box-sizing: border-box;
}
.button--green {
background-color: #22BC66;
border-top: 10px solid #22BC66;
border-right: 18px solid #22BC66;
border-bottom: 10px solid #22BC66;
border-left: 18px solid #22BC66;
}
.button--red {
background-color: #FF6136;
border-top: 10px solid #FF6136;
border-right: 18px solid #FF6136;
border-bottom: 10px solid #FF6136;
border-left: 18px solid #FF6136;
}
@media only screen and (max-width: 500px) {
.button {
width: 100% !important;
text-align: center !important;
}
}
/* Attribute list ------------------------------ */
.attributes {
margin: 0 0 21px;
}
.attributes_content {
background-color: #F4F4F7;
padding: 16px;
}
.attributes_item {
padding: 0;
}
/* Related Items ------------------------------ */
.related {
width: 100%;
margin: 0;
padding: 25px 0 0 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.related_item {
padding: 10px 0;
color: #CBCCCF;
font-size: 15px;
line-height: 18px;
}
.related_item-title {
display: block;
margin: .5em 0 0;
}
.related_item-thumb {
display: block;
padding-bottom: 10px;
}
.related_heading {
border-top: 1px solid #CBCCCF;
text-align: center;
padding: 25px 0 10px;
}
/* Discount Code ------------------------------ */
.discount {
width: 100%;
margin: 0;
padding: 24px;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #F4F4F7;
border: 2px dashed #CBCCCF;
}
.discount_heading {
text-align: center;
}
.discount_body {
text-align: center;
font-size: 15px;
}
/* Social Icons ------------------------------ */
.social {
width: auto;
}
.social td {
padding: 0;
width: auto;
}
.social_icon {
height: 20px;
margin: 0 8px 10px 8px;
padding: 0;
}
/* Data table ------------------------------ */
.purchase {
width: 100%;
margin: 0;
padding: 35px 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.purchase_content {
width: 100%;
margin: 0;
padding: 25px 0 0 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.purchase_item {
padding: 10px 0;
color: #51545E;
font-size: 15px;
line-height: 18px;
}
.purchase_heading {
padding-bottom: 8px;
border-bottom: 1px solid #EAEAEC;
}
.purchase_heading p {
margin: 0;
color: #85878E;
font-size: 12px;
}
.purchase_footer {
padding-top: 15px;
border-top: 1px solid #EAEAEC;
}
.purchase_total {
margin: 0;
text-align: right;
font-weight: bold;
color: #333333;
}
.purchase_total--label {
padding: 0 15px 0 0;
}
body {
background-color: #F2F4F6;
color: #51545E;
}
p {
color: #51545E;
}
.email-wrapper {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #F2F4F6;
}
.email-content {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
/* Masthead ----------------------- */
.email-masthead {
padding: 25px 0;
text-align: center;
}
.email-masthead_logo {
width: 94px;
}
.email-masthead_name {
font-size: 16px;
font-weight: bold;
color: #A8AAAF;
text-decoration: none;
text-shadow: 0 1px 0 white;
}
/* Body ------------------------------ */
.email-body {
width: 100%;
margin: 0;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
}
.email-body_inner {
width: 570px;
margin: 0 auto;
padding: 0;
-premailer-width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
background-color: #FFFFFF;
}
.email-footer {
width: 570px;
margin: 0 auto;
padding: 0;
-premailer-width: 570px;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
text-align: center;
}
.email-footer p {
color: #A8AAAF;
}
.body-action {
width: 100%;
margin: 30px auto;
padding: 0;
-premailer-width: 100%;
-premailer-cellpadding: 0;
-premailer-cellspacing: 0;
text-align: center;
}
.body-sub {
margin-top: 25px;
padding-top: 25px;
border-top: 1px solid #EAEAEC;
}
.content-cell {
padding: 45px;
}
/*Media Queries ------------------------------ */
@media only screen and (max-width: 600px) {
.email-body_inner,
.email-footer {
width: 100% !important;
}
}
@media (prefers-color-scheme: dark) {
body,
.email-body,
.email-body_inner,
.email-content,
.email-wrapper,
.email-masthead,
.email-footer {
background-color: #333333 !important;
color: #FFF !important;
}
p,
ul,
ol,
blockquote,
h1,
h2,
h3,
span,
.purchase_item {
color: #FFF !important;
}
.attributes_content,
.discount {
background-color: #222 !important;
}
.email-masthead_name {
text-shadow: none !important;
}
}
:root {
color-scheme: light dark;
supported-color-schemes: light dark;
}
body { width:100% !important; height:100%; margin:0; -webkit-text-size-adjust:none; }
a { color:#3869D4; }
a img { border:none; }
td { word-break:break-word; }
.preheader { display:none !important; visibility:hidden; mso-hide:all; font-size:1px; line-height:1px; max-height:0; max-width:0; opacity:0; overflow:hidden; }
body, td, th { font-family:"Nunito Sans",Helvetica,Arial,sans-serif; }
h1 { margin-top:0; color:#333333; font-size:22px; font-weight:bold; text-align:left; }
h2 { margin-top:0; color:#333333; font-size:16px; font-weight:bold; text-align:left; }
h3 { margin-top:0; color:#333333; font-size:14px; font-weight:bold; text-align:left; }
td, th { font-size:16px; }
p, ul, ol, blockquote { margin:.4em 0 1.1875em; font-size:16px; line-height:1.625; }
p.sub { font-size:13px; }
.align-right { text-align:right; }
.align-left { text-align:left; }
.align-center { text-align:center; }
.u-margin-bottom-none { margin-bottom:0; }
.button { background-color:#3869D4; border-top:10px solid #3869D4; border-right:18px solid #3869D4; border-bottom:10px solid #3869D4; border-left:18px solid #3869D4; display:inline-block; color:#FFF; text-decoration:none; border-radius:3px; box-shadow:0 2px 3px rgba(0,0,0,0.16); -webkit-text-size-adjust:none; box-sizing:border-box; }
.button--green { background-color:#22BC66; border-top:10px solid #22BC66; border-right:18px solid #22BC66; border-bottom:10px solid #22BC66; border-left:18px solid #22BC66; }
.button--red { background-color:#FF6136; border-top:10px solid #FF6136; border-right:18px solid #FF6136; border-bottom:10px solid #FF6136; border-left:18px solid #FF6136; }
@media only screen and (max-width:500px) { .button { width:100% !important; text-align:center !important; } }
.attributes { margin:0 0 21px; }
.attributes_content { background-color:#F4F4F7; padding:16px; }
.attributes_item { padding:0; }
.related { width:100%; margin:0; padding:25px 0 0 0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.related_item { padding:10px 0; color:#CBCCCF; font-size:15px; line-height:18px; }
.related_item-title { display:block; margin:.5em 0 0; }
.related_item-thumb { display:block; padding-bottom:10px; }
.related_heading { border-top:1px solid #CBCCCF; text-align:center; padding:25px 0 10px; }
.discount { width:100%; margin:0; padding:24px; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; background-color:#F4F4F7; border:2px dashed #CBCCCF; }
.discount_heading { text-align:center; }
.discount_body { text-align:center; font-size:15px; }
.social { width:auto; }
.social td { padding:0; width:auto; }
.social_icon { height:20px; margin:0 8px 10px 8px; padding:0; }
.purchase { width:100%; margin:0; padding:35px 0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.purchase_content { width:100%; margin:0; padding:25px 0 0 0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.purchase_item { padding:10px 0; color:#51545E; font-size:15px; line-height:18px; }
.purchase_heading { padding-bottom:8px; border-bottom:1px solid #EAEAEC; }
.purchase_heading p { margin:0; color:#85878E; font-size:12px; }
.purchase_footer { padding-top:15px; border-top:1px solid #EAEAEC; }
.purchase_total { margin:0; text-align:right; font-weight:bold; color:#333333; }
.purchase_total--label { padding:0 15px 0 0; }
body { background-color:#F2F4F6; color:#51545E; }
p { color:#51545E; }
.email-wrapper { width:100%; margin:0; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; background-color:#F2F4F6; }
.email-content { width:100%; margin:0; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.email-masthead { padding:25px 0; text-align:center; }
.email-masthead_logo { width:94px; }
.email-masthead_name { font-size:16px; font-weight:bold; color:#A8AAAF; text-decoration:none; text-shadow:0 1px 0 white; }
.email-body { width:100%; margin:0; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
.email-body_inner { width:570px; margin:0 auto; padding:0; -premailer-width:570px; -premailer-cellpadding:0; -premailer-cellspacing:0; background-color:#FFFFFF; }
.email-footer { width:570px; margin:0 auto; padding:0; -premailer-width:570px; -premailer-cellpadding:0; -premailer-cellspacing:0; text-align:center; }
.email-footer p { color:#A8AAAF; }
.body-action { width:100%; margin:30px auto; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; text-align:center; }
.body-sub { margin-top:25px; padding-top:25px; border-top:1px solid #EAEAEC; }
.content-cell { padding:45px; }
@media only screen and (max-width:600px) { .email-body_inner, .email-footer { width:100% !important; } }
@media (prefers-color-scheme:dark) { body,.email-body,.email-body_inner,.email-content,.email-wrapper,.email-masthead,.email-footer { background-color:#333333 !important; color:#FFF !important; } p,ul,ol,blockquote,h1,h2,h3,span,.purchase_item { color:#FFF !important; } .attributes_content,.discount { background-color:#222 !important; } .email-masthead_name { text-shadow:none !important; } }
:root { color-scheme:light dark; supported-color-schemes:light dark; }
</style>
<!--[if mso]>
<style type="text/css">
.f-fallback {
font-family: Arial, sans-serif;
}
</style>
<![endif]-->
</head>
<body>
<span class="preheader">Use this link to reset your password. The link is only valid for 12 hours.</span>
@@ -525,31 +169,24 @@ if(!empty($mail)){
<table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="email-masthead">
<a href="https://auth.jakach.ch" class="f-fallback email-masthead_name">
Jakach Login
</a>
<a href="https://auth.jakach.ch" class="f-fallback email-masthead_name">Jakach Login</a>
</td>
</tr>
<!-- Email Body -->
<tr>
<td class="email-body" width="570" cellpadding="0" cellspacing="0">
<table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<!-- Body content -->
<tr>
<td class="content-cell">
<div class="f-fallback">
<h1>Hi '.$username.',</h1>
<h1>Hi '.$html_username.',</h1>
<p>You recently requested to reset your password for your Jakach login account. Use the button below to reset it. <strong>This password reset is only valid for the next 12 hours.</strong></p>
<!-- Action -->
<table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<!-- Border based button
https://litmus.com/blog/a-guide-to-bulletproof-buttons-in-email-design -->
<table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr>
<td align="center">
<a href="'.$link.'" class="f-fallback button button--green" target="_blank">Reset your password</a>
<a href="'.$html_link.'" class="f-fallback button button--green" target="_blank">Reset your password</a>
</td>
</tr>
</table>
@@ -559,19 +196,17 @@ if(!empty($mail)){
<p><strong>Request Details:</strong></p>
<ul>
<li><strong>Date & Time:</strong> '.$date.'</li>
<li><strong>Device & Browser:</strong> '.$device.'</li>
<li><strong>Account:</strong> '.$mail.'</li>
<li><strong>IP Address:</strong> '.$ip.'</li>
<li><strong>Location:</strong> '.$loc.'</li>
<li><strong>Device & Browser:</strong> '.$html_device.'</li>
<li><strong>Account:</strong> '.$html_mail.'</li>
<li><strong>IP Address:</strong> '.$html_ip.'</li>
<li><strong>Location:</strong> '.$html_loc.'</li>
</ul>
<p>Thanks,
<br>The Jakach login team</p>
<!-- Sub copy -->
<p>Thanks,<br>The Jakach login team</p>
<table class="body-sub" role="presentation">
<tr>
<td>
<p class="f-fallback sub">If youre having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">'.$link.'</p>
<p class="f-fallback sub">If you are having trouble with the button above, copy and paste the URL below into your web browser.</p>
<p class="f-fallback sub">'.$html_link.'</p>
</td>
</tr>
</table>
@@ -586,10 +221,7 @@ if(!empty($mail)){
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td class="content-cell" align="center">
<p class="f-fallback sub align-center">
Jakach.ch
<br>CH-Switzerland.
</p>
<p class="f-fallback sub align-center">Jakach.ch<br>CH-Switzerland.</p>
</td>
</tr>
</table>
+19 -8
View File
@@ -16,21 +16,32 @@ if ($conn->connect_error) {
}
$search = trim($_GET['search'] ?? '');
$sort = $_GET['sort'] ?? 'id';
$sort = $_GET['sort'] ?? '';
$order = strtoupper($_GET['order'] ?? 'ASC') === 'DESC' ? 'DESC' : 'ASC';
$allowedSorts = ['id', 'username'];
if (!in_array($sort, $allowedSorts)) {
$sort = 'id';
}
if ($search !== '') {
$query = "SELECT id, username FROM users WHERE username LIKE ? ORDER BY $sort $order";
if ($sort === 'username') {
$query = $order === 'DESC'
? "SELECT id, username FROM users WHERE username LIKE ? ORDER BY username DESC"
: "SELECT id, username FROM users WHERE username LIKE ? ORDER BY username ASC";
} else {
$query = $order === 'DESC'
? "SELECT id, username FROM users WHERE username LIKE ? ORDER BY id DESC"
: "SELECT id, username FROM users WHERE username LIKE ? ORDER BY id ASC";
}
$stmt = $conn->prepare($query);
$like = '%' . $search . '%';
$stmt->bind_param('s', $like);
} else {
$query = "SELECT id, username FROM users ORDER BY $sort $order";
if ($sort === 'username') {
$query = $order === 'DESC'
? "SELECT id, username FROM users ORDER BY username DESC"
: "SELECT id, username FROM users ORDER BY username ASC";
} else {
$query = $order === 'DESC'
? "SELECT id, username FROM users ORDER BY id DESC"
: "SELECT id, username FROM users ORDER BY id ASC";
}
$stmt = $conn->prepare($query);
}
+39 -15
View File
@@ -1,19 +1,25 @@
<?php
function secure_cookie_options(array $overrides = []): array
{
return array_merge([
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
], $overrides);
}
function secure_session_start(): void
{
if (session_status() === PHP_SESSION_ACTIVE) {
return;
}
session_set_cookie_params([
session_set_cookie_params(secure_cookie_options([
'lifetime' => 0,
'path' => '/',
'domain' => '',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
]));
session_start();
}
@@ -242,13 +248,9 @@ function clear_rate_limit(mysqli $conn, string $bucket, string $identifier = '')
function set_secure_cookie(string $name, string $value, int $expires): void
{
setcookie($name, $value, [
setcookie($name, $value, secure_cookie_options([
'expires' => $expires,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
]));
}
function delete_cookie(string $name): void
@@ -280,6 +282,10 @@ function normalize_redirect_target(?string $target): string
return '/account/';
}
if (normalize_redirect_host($parts['host']) === null) {
return '/account/';
}
return $target;
}
@@ -308,12 +314,11 @@ function is_external_domain(string $url): ?string
return null;
}
$host = parse_url($url, PHP_URL_HOST);
if ($host === null || $host === '') {
$host = normalize_redirect_host((string) parse_url($url, PHP_URL_HOST));
if ($host === null) {
return null;
}
$host = strtolower($host);
if ($host === 'auth.jakach.ch' || str_ends_with($host, '.jakach.ch')) {
return null;
}
@@ -321,4 +326,23 @@ function is_external_domain(string $url): ?string
return $host;
}
function normalize_redirect_host(string $host): ?string
{
$host = rtrim(strtolower(trim($host)), '.');
if ($host === '' || strlen($host) > 253 || preg_match('/[\x00-\x20\x7f<>"\'`]/', $host)) {
return null;
}
if (filter_var($host, FILTER_VALIDATE_IP)) {
return $host;
}
if (!filter_var($host, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME)) {
return null;
}
return $host;
}
?>
+2 -2
View File
@@ -1,10 +1,10 @@
services:
jakach-login-db:
image: yobasystems/alpine-mariadb:latest
image: dhi.io/mariadb:12
container_name: jakach-login-db
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: 1234
MARIADB_ROOT_PASSWORD: 1234
networks:
jakach-login-network:
ipv4_address: 192.168.5.2
+2 -2
View File
@@ -7,8 +7,8 @@ RUN apt-get update && \
pecl install redis && \
docker-php-ext-enable redis
# Enable SSL module for Apache
RUN a2enmod ssl
# Enable Apache modules
RUN a2enmod ssl headers
# Restart Apache to apply changes
RUN service apache2 restart