Compare commits

...

12 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
8 changed files with 581 additions and 499 deletions
+450 -20
View File
@@ -2,15 +2,268 @@ name: Deploy
on: on:
push: 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: 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: deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
needs:
- security_scan
- code_scan
steps: steps:
- name: Install SSH client - name: Install dependencies
run: | run: |
set -e set -e
if command -v apk >/dev/null 2>&1; then if command -v apk >/dev/null 2>&1; then
apk add --no-cache openssh-client git bash apk add --no-cache openssh-client git bash
elif command -v apt-get >/dev/null 2>&1; then elif command -v apt-get >/dev/null 2>&1; then
@@ -21,55 +274,232 @@ jobs:
elif command -v yum >/dev/null 2>&1; then elif command -v yum >/dev/null 2>&1; then
yum install -y openssh-clients git bash yum install -y openssh-clients git bash
else else
echo "No supported package manager found" echo "Unsupported package manager"
exit 1 exit 1
fi fi
- name: Run deploy
- name: Deploy application
env: env:
SSH_KEY: ${{ secrets.SSH_KEY }} SSH_KEY: ${{ secrets.SSH_KEY }}
SSH_USER: ${{ vars.SSH_USER }} SSH_USER: ${{ vars.SSH_USER }}
SSH_IP: ${{ vars.SSH_IP }} SSH_IP: ${{ vars.SSH_IP }}
GIT_USER: ${{ vars.GIT_USER }} GIT_USER: ${{ vars.GIT_USER }}
GIT_TOKEN: ${{ secrets.GIT_TOKEN }} GIT_TOKEN: ${{ secrets.GIT_TOKEN }}
APP_DIR: /home/deploy/my-app
GIT_REPO: Jakach/my-app.git
GIT_BRANCH: main
run: | run: |
cat > deploy.sh <<'EOF' cat > deploy.sh <<'OUTER_EOF'
#!/usr/bin/env bash #!/usr/bin/env bash
set -Eeuo pipefail set -Eeuo pipefail
: "${SSH_KEY:?SSH_KEY is required}" : "${SSH_KEY:?SSH_KEY is required}"
: "${SSH_USER:?SSH_USER is required}" : "${SSH_USER:?SSH_USER is required}"
: "${SSH_IP:?SSH_IP is required}" : "${SSH_IP:?SSH_IP is required}"
: "${GIT_USER:?GIT_USER is required}" : "${GIT_USER:?GIT_USER is required}"
: "${GIT_TOKEN:?GIT_TOKEN is required}" : "${GIT_TOKEN:?GIT_TOKEN is required}"
APP_DIR="/srv/systems/jakach-login" REPO_NAME="$(basename "$GIT_REPO")"
GIT_HOST="${GIT_HOST:-git.jakach.ch}" APP_DIR="/srv/systems/${REPO_NAME}"
GIT_REPO="jakach/jakach-login.git"
GIT_BRANCH="${GIT_BRANCH:-main}"
mkdir -p ~/.ssh mkdir -p ~/.ssh
chmod 700 ~/.ssh chmod 700 ~/.ssh
printf '%s\n' "$SSH_KEY" | tr -d '\r' > ~/.ssh/deploy_key printf '%s\n' "$SSH_KEY" | tr -d '\r' > ~/.ssh/deploy_key
chmod 600 ~/.ssh/deploy_key chmod 600 ~/.ssh/deploy_key
ssh-keyscan -H "$SSH_IP" >> ~/.ssh/known_hosts 2>/dev/null || true 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 StrictHostKeyChecking=yes \
-o IdentitiesOnly=yes \ -o IdentitiesOnly=yes \
"$SSH_USER@$SSH_IP" \ "$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 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" 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 fetch origin "$GIT_BRANCH"
git checkout "$GIT_BRANCH" git checkout "$GIT_BRANCH"
git pull origin "$GIT_BRANCH" git pull origin "$GIT_BRANCH"
docker compose down
docker compose up -d --build # --------------------------------------------------
REMOTE # Create nginx reverse proxy entry
EOF # --------------------------------------------------
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
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 chmod +x deploy.sh
./deploy.sh ./deploy.sh
+6 -7
View File
@@ -14,19 +14,18 @@ Using Jakach Login is straightforward:
1. **Clone the repository:** 1. **Clone the repository:**
```bash ```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:** 3. **Create a Docker volume for database storage:**
```bash ```bash
docker volume create jakach-login-db-storage 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 ```bash
docker-compose up docker-compose up
``` ```
+7 -2
View File
@@ -10,6 +10,11 @@ secure_session_start();
require_same_origin_request(); require_same_origin_request();
require_csrf_token(); 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 // Assuming you've already established a database connection here
include "../../config/config.php"; include "../../config/config.php";
$conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD,$DB_DATABASE); $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); $createArgs = $WebAuthn->getCreateArgs(\hex2bin($userId), $userName, $userDisplayName, 60*4, $requireResidentKey, $userVerification, $crossPlatformAttachment);
header('Content-Type: application/json'); 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. // save challange to session. you have to deliver it to processGet later.
$_SESSION['challenge'] = $WebAuthn->getChallenge(); $_SESSION['challenge'] = $WebAuthn->getChallenge();
@@ -138,7 +143,7 @@ try {
$getArgs = $WebAuthn->getGetArgs($ids, 60*4, $typeUsb, $typeNfc, $typeBle, $typeHyb, $typeInt, $userVerification); $getArgs = $WebAuthn->getGetArgs($ids, 60*4, $typeUsb, $typeNfc, $typeBle, $typeHyb, $typeInt, $userVerification);
header('Content-Type: application/json'); 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. // save challange to session. you have to deliver it to processGet later.
$_SESSION['challenge'] = $WebAuthn->getChallenge(); $_SESSION['challenge'] = $WebAuthn->getChallenge();
+3 -4
View File
@@ -43,7 +43,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
} }
// Sanitize and validate the input // 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']); $email = trim((string) $data['email']);
if ($email !== "" && !filter_var($email, FILTER_VALIDATE_EMAIL)) { if ($email !== "" && !filter_var($email, FILTER_VALIDATE_EMAIL)) {
echo json_encode([ echo json_encode([
@@ -52,7 +52,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
]); ]);
exit(); 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 //check if username is allready taken
$id_check=0; $id_check=0;
@@ -63,8 +63,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
mysqli_stmt_store_result($stmt); mysqli_stmt_store_result($stmt);
mysqli_stmt_bind_result($stmt, $id_check); mysqli_stmt_bind_result($stmt, $id_check);
mysqli_stmt_fetch($stmt); mysqli_stmt_fetch($stmt);
if(mysqli_stmt_num_rows($stmt) > 0 && $username!==$name){ if((mysqli_stmt_num_rows($stmt) > 0 && $username!==$name) || $name === ""){
//this username is allready taken
echo json_encode([ echo json_encode([
'success' => false, 'success' => false,
'message' => 'Username allready taken. Please choose another username.' 'message' => 'Username allready taken. Please choose another username.'
+8 -2
View File
@@ -7,6 +7,12 @@ include "../utils/security.php";
secure_session_start(); secure_session_start();
require_same_origin_request(); require_same_origin_request();
require_csrf_token(); 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"; include "../../config/config.php";
$conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD,$DB_DATABASE); $conn = new mysqli($DB_SERVERNAME, $DB_USERNAME, $DB_PASSWORD,$DB_DATABASE);
if ($conn->connect_error) { if ($conn->connect_error) {
@@ -90,7 +96,7 @@ try {
// Get create arguments // Get create arguments
$createArgs = $WebAuthn->getCreateArgs(\hex2bin($userId), $userName, $userDisplayName, 60*4, $requireResidentKey, $userVerification); $createArgs = $WebAuthn->getCreateArgs(\hex2bin($userId), $userName, $userDisplayName, 60*4, $requireResidentKey, $userVerification);
header('Content-Type: application/json'); header('Content-Type: application/json');
print(json_encode($createArgs)); print_json_response($createArgs);
// Save challenge to session or somewhere else if needed // Save challenge to session or somewhere else if needed
} else if ($fn === 'getGetArgs') { } else if ($fn === 'getGetArgs') {
@@ -120,7 +126,7 @@ try {
$getArgs = $WebAuthn->getGetArgs($ids, 60*4, $typeUsb, $typeNfc, $typeBle, $typeHyb, $typeInt, $userVerification); $getArgs = $WebAuthn->getGetArgs($ids, 60*4, $typeUsb, $typeNfc, $typeBle, $typeHyb, $typeInt, $userVerification);
header('Content-Type: application/json'); 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. // save challange to session. you have to deliver it to processGet later.
$_SESSION['challenge'] = $WebAuthn->getChallenge(); $_SESSION['challenge'] = $WebAuthn->getChallenge();
+85 -453
View File
@@ -35,7 +35,6 @@ if (!$user_found) {
} }
//send telegram message //send telegram message
$device = $_SERVER['HTTP_USER_AGENT'] ?? ""; $device = $_SERVER['HTTP_USER_AGENT'] ?? "";
//$ip=$_SERVER["REMOTE_ADDR"];
$forwarded_for = $_SERVER["HTTP_X_FORWARDED_FOR"] ?? $_SERVER["REMOTE_ADDR"] ?? ""; $forwarded_for = $_SERVER["HTTP_X_FORWARDED_FOR"] ?? $_SERVER["REMOTE_ADDR"] ?? "";
$ip=trim(explode(",",$forwarded_for)[0]); $ip=trim(explode(",",$forwarded_for)[0]);
$location=get_location_from_ip($ip); $location=get_location_from_ip($ip);
@@ -44,15 +43,20 @@ $token=bin2hex(random_bytes(128));
$token_hash=auth_token_hash($token); $token_hash=auth_token_hash($token);
$link="https://auth.jakach.ch/login/reset_pw.php?token=$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" $message = "*Password reset token*\n\n"
. "You have requested the reset of your password here is your reset link.\n\n" . "You have requested the reset of your password here is your reset link.\n\n"
. "*Link*: [click here]($link)\n\n" . "*Link*: [click here]($link)\n\n"
. "*Details of this request:*\n" . "*Details of this request:*\n"
. "• *Date&Time*: $date\n" . "• *Date&Time*: $date\n"
. "• *Device&Browser*: $device\n" . "• *Device&Browser*: $tg_device\n"
. "*Location*: ".$location["country"].", ".$location["state"].", ".$location["city"]."\n" . "*Location*: $tg_location\n"
. "• *Account*: ".$_SESSION["username"]."\n" . "• *Account*: $tg_username\n"
. "• *IP*: $ip\n\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" ."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!*"; . "*Thank you for using Jakach login!*";
@@ -78,6 +82,12 @@ curl_close($ch);
//send mail //send mail
if(!empty($mail)){ if(!empty($mail)){
$loc=$location["country"].", ".$location["state"].", ".$location["city"]; $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 = ' $content = '
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <!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"> <html xmlns="http://www.w3.org/1999/xhtml">
@@ -89,433 +99,67 @@ if(!empty($mail)){
<meta name="supported-color-schemes" content="light dark" /> <meta name="supported-color-schemes" content="light dark" />
<title></title> <title></title>
<style type="text/css" rel="stylesheet" media="all"> <style type="text/css" rel="stylesheet" media="all">
/* Base ------------------------------ */
@import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap"); @import url("https://fonts.googleapis.com/css?family=Nunito+Sans:400,700&display=swap");
body { body { width:100% !important; height:100%; margin:0; -webkit-text-size-adjust:none; }
width: 100% !important; a { color:#3869D4; }
height: 100%; a img { border:none; }
margin: 0; td { word-break:break-word; }
-webkit-text-size-adjust: none; .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; }
a { h2 { margin-top:0; color:#333333; font-size:16px; font-weight:bold; text-align:left; }
color: #3869D4; 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; }
a img { p.sub { font-size:13px; }
border: none; .align-right { text-align:right; }
} .align-left { text-align:left; }
.align-center { text-align:center; }
td { .u-margin-bottom-none { margin-bottom:0; }
word-break: break-word; .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; }
.preheader { @media only screen and (max-width:500px) { .button { width:100% !important; text-align:center !important; } }
display: none !important; .attributes { margin:0 0 21px; }
visibility: hidden; .attributes_content { background-color:#F4F4F7; padding:16px; }
mso-hide: all; .attributes_item { padding:0; }
font-size: 1px; .related { width:100%; margin:0; padding:25px 0 0 0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
line-height: 1px; .related_item { padding:10px 0; color:#CBCCCF; font-size:15px; line-height:18px; }
max-height: 0; .related_item-title { display:block; margin:.5em 0 0; }
max-width: 0; .related_item-thumb { display:block; padding-bottom:10px; }
opacity: 0; .related_heading { border-top:1px solid #CBCCCF; text-align:center; padding:25px 0 10px; }
overflow: hidden; .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; }
/* Type ------------------------------ */ .discount_body { text-align:center; font-size:15px; }
.social { width:auto; }
body, .social td { padding:0; width:auto; }
td, .social_icon { height:20px; margin:0 8px 10px 8px; padding:0; }
th { .purchase { width:100%; margin:0; padding:35px 0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
font-family: "Nunito Sans", Helvetica, Arial, sans-serif; .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; }
h1 { .purchase_heading p { margin:0; color:#85878E; font-size:12px; }
margin-top: 0; .purchase_footer { padding-top:15px; border-top:1px solid #EAEAEC; }
color: #333333; .purchase_total { margin:0; text-align:right; font-weight:bold; color:#333333; }
font-size: 22px; .purchase_total--label { padding:0 15px 0 0; }
font-weight: bold; body { background-color:#F2F4F6; color:#51545E; }
text-align: left; 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; }
h2 { .email-masthead { padding:25px 0; text-align:center; }
margin-top: 0; .email-masthead_logo { width:94px; }
color: #333333; .email-masthead_name { font-size:16px; font-weight:bold; color:#A8AAAF; text-decoration:none; text-shadow:0 1px 0 white; }
font-size: 16px; .email-body { width:100%; margin:0; padding:0; -premailer-width:100%; -premailer-cellpadding:0; -premailer-cellspacing:0; }
font-weight: bold; .email-body_inner { width:570px; margin:0 auto; padding:0; -premailer-width:570px; -premailer-cellpadding:0; -premailer-cellspacing:0; background-color:#FFFFFF; }
text-align: left; .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; }
h3 { .body-sub { margin-top:25px; padding-top:25px; border-top:1px solid #EAEAEC; }
margin-top: 0; .content-cell { padding:45px; }
color: #333333; @media only screen and (max-width:600px) { .email-body_inner, .email-footer { width:100% !important; } }
font-size: 14px; @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; } }
font-weight: bold; :root { color-scheme:light dark; supported-color-schemes:light dark; }
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;
}
</style> </style>
<!--[if mso]>
<style type="text/css">
.f-fallback {
font-family: Arial, sans-serif;
}
</style>
<![endif]-->
</head> </head>
<body> <body>
<span class="preheader">Use this link to reset your password. The link is only valid for 12 hours.</span> <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"> <table class="email-content" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr> <tr>
<td class="email-masthead"> <td class="email-masthead">
<a href="https://auth.jakach.ch" class="f-fallback email-masthead_name"> <a href="https://auth.jakach.ch" class="f-fallback email-masthead_name">Jakach Login</a>
Jakach Login
</a>
</td> </td>
</tr> </tr>
<!-- Email Body -->
<tr> <tr>
<td class="email-body" width="570" cellpadding="0" cellspacing="0"> <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"> <table class="email-body_inner" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<!-- Body content -->
<tr> <tr>
<td class="content-cell"> <td class="content-cell">
<div class="f-fallback"> <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> <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"> <table class="body-action" align="center" width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr> <tr>
<td align="center"> <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"> <table width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
<tr> <tr>
<td align="center"> <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> </td>
</tr> </tr>
</table> </table>
@@ -559,19 +196,17 @@ if(!empty($mail)){
<p><strong>Request Details:</strong></p> <p><strong>Request Details:</strong></p>
<ul> <ul>
<li><strong>Date & Time:</strong> '.$date.'</li> <li><strong>Date & Time:</strong> '.$date.'</li>
<li><strong>Device & Browser:</strong> '.$device.'</li> <li><strong>Device & Browser:</strong> '.$html_device.'</li>
<li><strong>Account:</strong> '.$mail.'</li> <li><strong>Account:</strong> '.$html_mail.'</li>
<li><strong>IP Address:</strong> '.$ip.'</li> <li><strong>IP Address:</strong> '.$html_ip.'</li>
<li><strong>Location:</strong> '.$loc.'</li> <li><strong>Location:</strong> '.$html_loc.'</li>
</ul> </ul>
<p>Thanks, <p>Thanks,<br>The Jakach login team</p>
<br>The Jakach login team</p>
<!-- Sub copy -->
<table class="body-sub" role="presentation"> <table class="body-sub" role="presentation">
<tr> <tr>
<td> <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">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">'.$link.'</p> <p class="f-fallback sub">'.$html_link.'</p>
</td> </td>
</tr> </tr>
</table> </table>
@@ -586,10 +221,7 @@ if(!empty($mail)){
<table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation"> <table class="email-footer" align="center" width="570" cellpadding="0" cellspacing="0" role="presentation">
<tr> <tr>
<td class="content-cell" align="center"> <td class="content-cell" align="center">
<p class="f-fallback sub align-center"> <p class="f-fallback sub align-center">Jakach.ch<br>CH-Switzerland.</p>
Jakach.ch
<br>CH-Switzerland.
</p>
</td> </td>
</tr> </tr>
</table> </table>
+20 -9
View File
@@ -16,21 +16,32 @@ if ($conn->connect_error) {
} }
$search = trim($_GET['search'] ?? ''); $search = trim($_GET['search'] ?? '');
$sort = $_GET['sort'] ?? 'id'; $sort = $_GET['sort'] ?? '';
$order = strtoupper($_GET['order'] ?? 'ASC') === 'DESC' ? 'DESC' : 'ASC'; $order = strtoupper($_GET['order'] ?? 'ASC') === 'DESC' ? 'DESC' : 'ASC';
$allowedSorts = ['id', 'username'];
if (!in_array($sort, $allowedSorts)) {
$sort = 'id';
}
if ($search !== '') { 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); $stmt = $conn->prepare($query);
$like = '%' . $search . '%'; $like = '%' . $search . '%';
$stmt->bind_param('s', $like); $stmt->bind_param('s', $like);
} else { } 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); $stmt = $conn->prepare($query);
} }
@@ -50,4 +61,4 @@ $stmt->close();
$conn->close(); $conn->close();
echo json_encode(['success' => true, 'data' => $users]); echo json_encode(['success' => true, 'data' => $users]);
?> ?>
+2 -2
View File
@@ -1,10 +1,10 @@
services: services:
jakach-login-db: jakach-login-db:
image: yobasystems/alpine-mariadb:latest image: dhi.io/mariadb:12
container_name: jakach-login-db container_name: jakach-login-db
restart: unless-stopped restart: unless-stopped
environment: environment:
MYSQL_ROOT_PASSWORD: 1234 MARIADB_ROOT_PASSWORD: 1234
networks: networks:
jakach-login-network: jakach-login-network:
ipv4_address: 192.168.5.2 ipv4_address: 192.168.5.2