diff --git a/.github/workflows/deploy-bot-on-prod.yml b/.github/workflows/deploy-bot-on-prod.yml index 547e4484..8efd1c94 100644 --- a/.github/workflows/deploy-bot-on-prod.yml +++ b/.github/workflows/deploy-bot-on-prod.yml @@ -13,13 +13,19 @@ jobs: name: prod_deploy if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: - - name: copy service file + - uses: actions/checkout@v2 + - name: Prepare infra/prod + run: | + mkdir ../build + cp -TR ./infra/prod ../build + tar -cvf deploy.tar ../build/ + - name: copy infra/prod uses: appleboy/scp-action@v0.1.4 with: host: ${{ secrets.VM_HOST }} username: ${{ secrets.VM_USER }} password: ${{ secrets.VM_PASSWORD }} - source: "infra/prod/" + source: "deploy.tar" target: /home/deploy/spread_wings_bot/infra/prod/ - name: ssh pull and start uses: appleboy/ssh-action@master @@ -29,6 +35,8 @@ jobs: password: ${{ secrets.VM_PASSWORD }} script: | cd /home/deploy/spread_wings_bot/infra/prod/ + tar -xvf deploy.tar --strip-components 1 + rm deploy.tar rm .env touch .env @@ -69,16 +77,14 @@ jobs: docker system prune --force # Installing defend service for app - # Шаг с копированием в строках 16-23 можно заменить командой ниже - нужно тестировать - # scp infra/prod/spread_wings_bot.service ${{ secrets.VM_USER }}@${{ secrets.VM_HOST }}:/spread_wings_bot/infra/prod/ - sudo cp -f /home/deploy/spread_wings_bot/infra/prod/spread_wings_bot.service /etc/systemd/system/spread_wings_bot.service + sudo cp -f /home/deploy/spread_wings_bot/infra/prod/spread_wings_bot_prod.service /etc/systemd/system/spread_wings_bot_prod.service sudo systemctl daemon-reload - sudo systemctl restart spread_wings_bot.service + sudo systemctl restart spread_wings_bot_prod.service # Installing the app - docker-compose -f docker-compose.stage.yaml stop - docker-compose -f docker-compose.stage.yaml pull - docker-compose -f docker-compose.stage.yaml up -d + docker-compose -f docker-compose.prod.yaml stop + docker-compose -f docker-compose.prod.yaml pull + docker-compose -f docker-compose.prod.yaml up -d # Applying initialization commands docker exec spread-wings-bot python manage.py migrate diff --git a/Makefile b/Makefile index 6c173211..ed82e952 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ help: # Show help .PHONY: runbot-init -runbot-init: deletedb rundb migrate filldb collectstatic createsuperuser runbot-db # Build and run Database Docker-image +runbot-init: deletedb rundb migrate filldb collectstatic runbot-db # Build and run Database Docker-image @echo -e "$(COLOR_YELLOW)Starting initialization...$(COLOR_RESET)" @source $$(poetry env info -p)/bin/activate diff --git a/infra/dev/nginx.stage.conf.template b/infra/dev/nginx.stage.conf.template index f6a7bbd1..f3c1f1d9 100644 --- a/infra/dev/nginx.stage.conf.template +++ b/infra/dev/nginx.stage.conf.template @@ -27,6 +27,14 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } + location /users/ { + proxy_pass http://spread-wings-bot:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + error_page 500 502 503 504 /50x.html; location = /50x.html { diff --git a/infra/prod/docker-compose.prod.yaml b/infra/prod/docker-compose.prod.yaml index b3e20a05..15d4ba98 100644 --- a/infra/prod/docker-compose.prod.yaml +++ b/infra/prod/docker-compose.prod.yaml @@ -60,7 +60,7 @@ services: ports: - "80:${NGINX_PORT}" volumes: - - ./nginx.stage.conf.template:/etc/nginx/templates/default.conf.template + - ./nginx.prod.conf.template:/etc/nginx/templates/default.conf.template - ../../static:/var/html/static/ - ../../media:/var/html/media/ - ./logs/nginx/:/var/log/nginx/ diff --git a/infra/prod/nginx.prod.conf.template b/infra/prod/nginx.prod.conf.template index ea1449a0..ac25d900 100644 --- a/infra/prod/nginx.prod.conf.template +++ b/infra/prod/nginx.prod.conf.template @@ -27,6 +27,13 @@ server { proxy_set_header X-Forwarded-Proto $scheme; } + location /users/ { + proxy_pass http://spread-wings-bot:8000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } error_page 500 502 503 504 /50x.html; diff --git a/infra/prod/spread_wings_bot.service b/infra/prod/spread_wings_bot_prod.service similarity index 100% rename from infra/prod/spread_wings_bot.service rename to infra/prod/spread_wings_bot_prod.service diff --git a/src/config/settings.py b/src/config/settings.py index c65ab9c0..b991ac10 100644 --- a/src/config/settings.py +++ b/src/config/settings.py @@ -116,7 +116,7 @@ EMAIL_BACKEND = env.str( "EMAIL_BACKEND", default="django.core.mail.backends.console.EmailBackend" ) -EMAIL_TEMPLATE_NAME = "email.html" +EMAIL_TEMPLATE_NAME = "emailing/email.html" EMAIL_HOST = env.str("EMAIL_HOST") try: EMAIL_PORT = env.int("EMAIL_PORT") diff --git a/src/users/admin.py b/src/users/admin.py index fe4a11e6..969acb28 100644 --- a/src/users/admin.py +++ b/src/users/admin.py @@ -2,6 +2,7 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin from django.contrib.auth.models import Group, Permission from django.utils.translation import gettext_lazy as _ +from django_otp.plugins.otp_email.models import EmailDevice from .forms import UserChangeForm, UserCreationForm from .models import User @@ -80,6 +81,17 @@ def reset_password(self, request, queryset): admin.site.unregister(Group) +class OTPDevice(admin.ModelAdmin): + """Empty class to hide OTPDevice model.""" + + def get_model_perms(self, request): + """Return empty perms dict thus hiding the model from admin.""" + return {} + + +admin.site.register(EmailDevice, OTPDevice) + + def permissions_new_unicode(self): """Translate default permissions.""" class_name = str(self.content_type) diff --git a/src/users/forms.py b/src/users/forms.py index e15f740b..b4327408 100644 --- a/src/users/forms.py +++ b/src/users/forms.py @@ -1,6 +1,7 @@ from django import forms from django.contrib.auth.forms import AuthenticationForm from django.contrib.auth.hashers import make_password +from django.db import transaction from django.utils.translation import gettext_lazy as _ from django.utils.translation import ngettext_lazy from django_otp.forms import OTPAuthenticationFormMixin @@ -57,6 +58,43 @@ class CustomOTPAuthenticationFormMixin(OTPAuthenticationFormMixin): "verification_not_allowed": _("Верификация недоступна"), } + def clean_otp(self, user): + """Perform OTP clean.""" + if user is None: + return + + validation_error = None + with transaction.atomic(): + try: + device = self._chosen_device(user) + token = self.cleaned_data.get("otp_token") + + user.otp_device = None + + try: + if self.cleaned_data.get("otp_challenge"): + self._handle_challenge(device) + elif token: + user.otp_device = self._verify_token( + user, token, device + ) + else: + self._handle_challenge(device) + raise forms.ValidationError( + self.otp_error_messages["token_required"], + code="token_required", + ) + finally: + if user.otp_device is None: + self._update_form(user) + except forms.ValidationError as e: + # Validation errors shouldn't abort the transaction, so we have + # to carefully transport them out. + validation_error = e + + if validation_error: + raise validation_error + def _chosen_device(self, user): """Return EmailDevise as default.""" return EmailDevice.objects.filter(user=user).first() diff --git a/src/users/templates/authentication/login.html b/src/users/templates/authentication/login.html index 1b911aa7..e7744288 100644 --- a/src/users/templates/authentication/login.html +++ b/src/users/templates/authentication/login.html @@ -83,7 +83,7 @@