6 Commits
Author SHA1 Message Date
ryan 7fa4c594e0 feat: update email configuration to use environment variables for credentials
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 4m1s
2026-10-28 16:28:30 -04:00
ryan 6586a38351 feat: add validation for production SSH credentials in deployment workflow
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 3m25s
2026-10-27 16:11:28 -04:00
ryan 220c86cac3 feat: add validation for production SSH credentials in deployment workflow
Chore App Build, Test, and Push Docker Images / build-and-push (push) Canceled after 12s
2026-10-27 15:51:48 -04:00
ryan cd3562babb feat: add validation for production SSH credentials in deployment workflow
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 4m25s
2026-10-27 15:44:41 -04:00
ryan 6946ddf7e5 feat: add validation for production SSH credentials in deployment workflow
Chore App Build, Test, and Push Docker Images / build-and-push (push) Canceled after 11s
2026-10-27 15:20:45 -04:00
ryan 7b4dc69241 feat: add validation for production SSH credentials in deployment workflow
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 5m33s
2026-10-21 15:23:41 -04:00
7 changed files with 69 additions and 10 deletions
+2
View File
@@ -1 +1,3 @@
FRONTEND_URL=https://yourdomain.com FRONTEND_URL=https://yourdomain.com
MAIL_USERNAME=your-gmail-account@gmail.com
MAIL_PASSWORD=your-16-character-app-password
+2
View File
@@ -163,6 +163,8 @@ jobs:
ADMIN_FIRST_NAME=${{ secrets.ADMIN_FIRST_NAME }} ADMIN_FIRST_NAME=${{ secrets.ADMIN_FIRST_NAME }}
ADMIN_LAST_NAME=${{ secrets.ADMIN_LAST_NAME }} ADMIN_LAST_NAME=${{ secrets.ADMIN_LAST_NAME }}
MONGO_URI=${{ secrets.MONGO_URI }} MONGO_URI=${{ secrets.MONGO_URI }}
MAIL_USERNAME=${{ secrets.MAIL_USER }}
MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}
USE_MONGODB=true USE_MONGODB=true
EOF EOF
+47
View File
@@ -191,12 +191,48 @@ jobs:
exit 1 exit 1
fi fi
- name: Validate production SSH credentials
env:
DEPLOY_PROD_HOST: ${{ secrets.DEPLOY_PROD_HOST }}
DEPLOY_PROD_USER: ${{ secrets.DEPLOY_PROD_USER }}
PROD_SSH_PRIVATE_KEY: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
PROD_SSH_KEY_PASSPHRASE: ${{ secrets.PROD_SSH_KEY_PASSPHRASE }}
run: |
set -eu
if [ -z "$DEPLOY_PROD_HOST" ]; then
echo "DEPLOY_PROD_HOST secret is required."
exit 1
fi
if [ -z "$DEPLOY_PROD_USER" ]; then
echo "DEPLOY_PROD_USER secret is required."
exit 1
fi
if [ -z "$PROD_SSH_PRIVATE_KEY" ]; then
echo "PROD_SSH_PRIVATE_KEY secret is required."
exit 1
fi
key_file=$(mktemp)
public_key_file=$(mktemp)
trap 'rm -f "$key_file" "$public_key_file"' EXIT
printf '%s\n' "$PROD_SSH_PRIVATE_KEY" > "$key_file"
chmod 600 "$key_file"
if ! ssh-keygen -y -P "$PROD_SSH_KEY_PASSPHRASE" -f "$key_file" > "$public_key_file"; then
echo "PROD_SSH_PRIVATE_KEY could not be read; check its format and PROD_SSH_KEY_PASSPHRASE."
exit 1
fi
echo "Production deploy key fingerprint:"
ssh-keygen -lf "$public_key_file"
- name: Deploy to production homeserver - name: Deploy to production homeserver
uses: appleboy/ssh-action@v1.0.3 uses: appleboy/ssh-action@v1.0.3
with: with:
host: ${{ secrets.DEPLOY_PROD_HOST }} host: ${{ secrets.DEPLOY_PROD_HOST }}
username: ${{ secrets.DEPLOY_PROD_USER }} username: ${{ secrets.DEPLOY_PROD_USER }}
key: ${{ secrets.PROD_SSH_PRIVATE_KEY }} key: ${{ secrets.PROD_SSH_PRIVATE_KEY }}
passphrase: ${{ secrets.PROD_SSH_KEY_PASSPHRASE }}
port: 22 port: 22
script: | script: |
set -euo pipefail set -euo pipefail
@@ -317,6 +353,14 @@ jobs:
exit 1 exit 1
fi fi
# Validate that MONGO_URI is present (falling back to previous .env if available)
if [ -z "${{ secrets.MONGO_URI }}" ]; then
echo "ERROR: MONGO_URI secret is missing or empty in Gitea repository secrets."
exit 1
else
echo "MONGO_URI successfully loaded from Gitea secrets."
fi
cat > .env << EOF cat > .env << EOF
FRONTEND_URL=${{ secrets.PROD_FRONTEND_URL }} FRONTEND_URL=${{ secrets.PROD_FRONTEND_URL }}
BACKEND_HOST_PORT=${{ secrets.PROD_BACKEND_HOST_PORT }} BACKEND_HOST_PORT=${{ secrets.PROD_BACKEND_HOST_PORT }}
@@ -330,6 +374,9 @@ jobs:
ADMIN_PASSWORD=${{ secrets.PROD_ADMIN_PASSWORD }} ADMIN_PASSWORD=${{ secrets.PROD_ADMIN_PASSWORD }}
ADMIN_FIRST_NAME=${{ secrets.PROD_ADMIN_FIRST_NAME }} ADMIN_FIRST_NAME=${{ secrets.PROD_ADMIN_FIRST_NAME }}
ADMIN_LAST_NAME=${{ secrets.PROD_ADMIN_LAST_NAME }} ADMIN_LAST_NAME=${{ secrets.PROD_ADMIN_LAST_NAME }}
MONGO_URI=${{ secrets.MONGO_URI }}
MAIL_USERNAME=${{ secrets.MAIL_USER }}
MAIL_PASSWORD=${{ secrets.MAIL_PASSWORD }}
EOF EOF
chmod 600 .env .rollback-meta chmod 600 .env .rollback-meta
+10 -6
View File
@@ -80,12 +80,16 @@ app.register_blueprint(push_subscription_api)
app.register_blueprint(digest_action_api) app.register_blueprint(digest_action_api)
app.config.update( app.config.update(
MAIL_SERVER='smtp.gmail.com', MAIL_SERVER=os.environ.get('MAIL_SERVER', 'smtp.gmail.com'),
MAIL_PORT=587, MAIL_PORT=int(os.environ.get('MAIL_PORT', '587')),
MAIL_USE_TLS=True, MAIL_USE_TLS=os.environ.get('MAIL_USE_TLS', 'true').lower() in ('1', 'true', 'yes'),
MAIL_USERNAME='ryan.kegel@gmail.com', MAIL_USERNAME=os.environ.get('MAIL_USERNAME'),
MAIL_PASSWORD='ruyj hxjf nmrz buar', MAIL_PASSWORD=os.environ.get('MAIL_PASSWORD'),
MAIL_DEFAULT_SENDER='ryan.kegel@gmail.com', MAIL_DEFAULT_SENDER=(
os.environ.get('MAIL_DEFAULT_SENDER')
or os.environ.get('MAIL_USERNAME')
or 'no-reply@reward-app.local'
),
FRONTEND_URL=os.environ.get('FRONTEND_URL', 'https://localhost:5173'), # Dynamic via env var, defaults to localhost FRONTEND_URL=os.environ.get('FRONTEND_URL', 'https://localhost:5173'), # Dynamic via env var, defaults to localhost
VAPID_CLAIMS_EMAIL=os.environ.get('VAPID_CLAIMS_EMAIL', 'admin@reward-app.local'), VAPID_CLAIMS_EMAIL=os.environ.get('VAPID_CLAIMS_EMAIL', 'admin@reward-app.local'),
) )
+2 -2
View File
@@ -147,9 +147,9 @@ def send_reset_password_email(to_email: str, token: str) -> None:
if os.environ.get('DB_ENV') == 'e2e': if os.environ.get('DB_ENV') == 'e2e':
return return
Mail(current_app).send(msg) Mail(current_app).send(msg)
print(f"[EMAIL to {to_email}] Reset password: {reset_url}") current_app.logger.info("Password reset email sent")
except Exception: except Exception:
print(f"Failed to send email to {to_email}. Reset link: {reset_url}") current_app.logger.exception("Failed to send password reset email")
def send_pin_setup_email(to_email: str, code: str) -> None: def send_pin_setup_email(to_email: str, code: str) -> None:
+2
View File
@@ -14,6 +14,8 @@ services:
- DIGEST_TOKEN_SECRET=${DIGEST_TOKEN_SECRET} - DIGEST_TOKEN_SECRET=${DIGEST_TOKEN_SECRET}
- VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY} - VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY}
- VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY} - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- SEED_EMAIL=${SEED_EMAIL} - SEED_EMAIL=${SEED_EMAIL}
- SEED_PASSWORD=${SEED_PASSWORD} - SEED_PASSWORD=${SEED_PASSWORD}
- SEED_PIN=${SEED_PIN} - SEED_PIN=${SEED_PIN}
+2
View File
@@ -15,6 +15,8 @@ services:
- DIGEST_TOKEN_SECRET=${DIGEST_TOKEN_SECRET} - DIGEST_TOKEN_SECRET=${DIGEST_TOKEN_SECRET}
- VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY} - VAPID_PUBLIC_KEY=${VAPID_PUBLIC_KEY}
- VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY} - VAPID_PRIVATE_KEY=${VAPID_PRIVATE_KEY}
- MAIL_USERNAME=${MAIL_USERNAME}
- MAIL_PASSWORD=${MAIL_PASSWORD}
- MONGO_URI=${MONGO_URI} - MONGO_URI=${MONGO_URI}
- DATA_ENV=prod - DATA_ENV=prod
- DB_ENV=prod - DB_ENV=prod