diff --git a/.env.sample b/.env.sample index c5ba6e8d..1177c816 100644 --- a/.env.sample +++ b/.env.sample @@ -14,7 +14,10 @@ MEDIA_ROOT=./files MEDIA_URL=https://example.com/geoshop_media/ ALLOWED_HOST=localhost,127.0.0.1,proxy.example.com -# Base URL of the api +# Base URL of the api: +# 1. forces the application to prepend a script name to all URLs generated by the reverse function and the {% url %} template tag +FORCE_SCRIPT_NAME= +# 2. add a prefix to all URL paths in the API ROOTURL= DEFAULT_FROM_EMAIL=no-reply@geoshop.ch diff --git a/default_settings.py b/default_settings.py index 9f82df89..97929ea7 100644 --- a/default_settings.py +++ b/default_settings.py @@ -231,16 +231,17 @@ USE_X_FORWARDED_HOST = True SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') -FORCE_SCRIPT_NAME = os.environ.get('ROOTURL', '') +FORCE_SCRIPT_NAME = os.environ.get('FORCE_SCRIPT_NAME', '') +ROOTURL=os.getenv('ROOTURL', '') SESSION_COOKIE_SECURE = True CSRF_COOKIE_SECURE = True # For large admin fields like order with order items DATA_UPLOAD_MAX_NUMBER_FIELDS = 5000 -STATIC_URL = FORCE_SCRIPT_NAME + '/static/' +STATIC_URL = FORCE_SCRIPT_NAME + ROOTURL + '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') MEDIA_ROOT = os.environ.get('MEDIA_ROOT', os.path.join(BASE_DIR, 'files')) -MEDIA_URL = os.environ.get('MEDIA_URL', FORCE_SCRIPT_NAME + '/files/') +MEDIA_URL = os.environ.get('MEDIA_URL', FORCE_SCRIPT_NAME + ROOTURL +'/files/') FRONT_PROTOCOL = os.environ["FRONT_PROTOCOL"] FRONT_URL = os.environ["FRONT_URL"] diff --git a/urls.py b/urls.py index 375b752a..094dcf95 100644 --- a/urls.py +++ b/urls.py @@ -19,6 +19,16 @@ from api import views import oidc +# Use ROOTURL from settings to prefix all urls +ROOTURL = getattr(settings, 'ROOTURL', '') + +# Ensure ROOTURL is properly formatted +if ROOTURL.startswith('/'): + ROOTURL = ROOTURL[1:] +if not ROOTURL.endswith('/') and not ROOTURL == '': + ROOTURL += '/' + + admin.site.site_header = _("GeoShop Administration") admin.site.site_title = _("GeoShop Admin") @@ -35,61 +45,60 @@ router.register(r'product', views.ProductViewSet, basename='product') router.register(r'productformat', views.ProductFormatViewSet) router.register(r'pricing', views.PricingViewSet) -router.register_additional_route_to_root('extract/order/', 'extract_order') -router.register_additional_route_to_root('extract/order/fake', 'extract_order_fake') -router.register_additional_route_to_root('extract/orderitem/', 'extract_orderitem') -router.register_additional_route_to_root('token', 'token_obtain_pair') -router.register_additional_route_to_root('token/refresh', 'token_refresh') -router.register_additional_route_to_root('token/verify', 'token_verify') -router.register_additional_route_to_root('auth/change', 'auth_change_user') -router.register_additional_route_to_root('auth/current', 'auth_current_user') -router.register_additional_route_to_root('auth/password', 'auth_password') -router.register_additional_route_to_root('auth/password/confirm', 'auth_password_confirm') -router.register_additional_route_to_root('auth/register', 'auth_register') +router.register_additional_route_to_root(f'{ROOTURL}extract/order/', 'extract_order') +router.register_additional_route_to_root(f'{ROOTURL}extract/order/fake', 'extract_order_fake') +router.register_additional_route_to_root(f'{ROOTURL}extract/orderitem/', 'extract_orderitem') +router.register_additional_route_to_root(f'{ROOTURL}token', 'token_obtain_pair') +router.register_additional_route_to_root(f'{ROOTURL}token/refresh', 'token_refresh') +router.register_additional_route_to_root(f'{ROOTURL}token/verify', 'token_verify') +router.register_additional_route_to_root(f'{ROOTURL}auth/change', 'auth_change_user') +router.register_additional_route_to_root(f'{ROOTURL}auth/current', 'auth_current_user') +router.register_additional_route_to_root(f'{ROOTURL}auth/password', 'auth_password') +router.register_additional_route_to_root(f'{ROOTURL}auth/password/confirm', 'auth_password_confirm') +router.register_additional_route_to_root(f'{ROOTURL}auth/register', 'auth_register') # Wire up our API using automatic URL routing. # Additionally, we include login URLs for the browsable API. urlpatterns = [ # this url is used to generate email content + #TODO FIXME: the favicon is not served correctly path('favicon.ico', RedirectView.as_view(url='{}api/favicon.ico'.format(settings.STATIC_URL))), - re_path(r'^auth/reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', + re_path(rf'^{ROOTURL}auth/reset/(?P[0-9A-Za-z_\-]+)/(?P[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$', TemplateView.as_view(), name='password_reset_confirm'), - path('auth/change/', views.UserChangeView.as_view(), name='auth_change_user'), - path('auth/current/', views.CurrentUserView.as_view(), name='auth_current_user'), - re_path(r'^download/(?P\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)$', + path(f'{ROOTURL}auth/change/', views.UserChangeView.as_view(), name='auth_change_user'), + path(f'{ROOTURL}auth/current/', views.CurrentUserView.as_view(), name='auth_current_user'), + re_path(rf'^{ROOTURL}download/(?P\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)$', views.OrderByUUIDView.as_view(), name='order_uuid'), - re_path(r'^download/(?P\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)/get_link', + re_path(rf'^{ROOTURL}download/(?P\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)/get_link$', views.DownloadLinkView.as_view(), name='order_uuid_link'), - path('auth/password/', views.PasswordResetView.as_view(), name='auth_password'), - path('auth/password/confirm', views.PasswordResetConfirmView.as_view(), name='auth_password_confirm'), - path('auth/verify-email/', views.VerifyEmailView.as_view(), name='auth_verify_email'), - re_path(r'^auth/account-confirm-email/(?P[-:\w]+)/$', TemplateView.as_view(), + path(f'{ROOTURL}auth/password/', views.PasswordResetView.as_view(),name='auth_password'), + path(f'{ROOTURL}auth/password/confirm', views.PasswordResetConfirmView.as_view(), name='auth_password_confirm'), + path(f'{ROOTURL}auth/verify-email/', views.VerifyEmailView.as_view(), name='auth_verify_email'), + re_path(rf'^{ROOTURL}auth/account-confirm-email/(?P[-:\w]+)/$', TemplateView.as_view(), name='account_confirm_email'), - path('auth/register/', views.RegisterView.as_view(), name='auth_register'), - path('extract/order/', views.ExtractOrderView.as_view(), name='extract_order'), - path('extract/orderitem/', views.ExtractOrderItemView.as_view(), name='extract_orderitem'), - re_path(r'^extract/orderitem/(?P[0-9]+)', + path(f'{ROOTURL}auth/register/', views.RegisterView.as_view(), name='auth_register'), + path(f'{ROOTURL}extract/order/', views.ExtractOrderView.as_view(), name='extract_order'), + path(f'{ROOTURL}extract/orderitem/', views.ExtractOrderItemView.as_view(), name='extract_orderitem'), + re_path(rf'^{ROOTURL}extract/orderitem/(?P[0-9]+)$', views.ExtractOrderItemView.as_view(), name='extract_orderitem'), - path('token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), - path('token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), - path('token/verify/', TokenVerifyView.as_view(), name='token_verify'), - path('session-auth/', include('rest_framework.urls', namespace='rest_framework')), - - re_path(r'^validate/orderitem/(?P[a-zA-Z0-9_-]+)$', + path(f'{ROOTURL}token/', TokenObtainPairView.as_view(), name='token_obtain_pair'), + path(f'{ROOTURL}token/refresh/', TokenRefreshView.as_view(), name='token_refresh'), + path(f'{ROOTURL}token/verify/', TokenVerifyView.as_view(), name='token_verify'), + path(f'{ROOTURL}session-auth/', include('rest_framework.urls', namespace='rest_framework')), + re_path(rf'^{ROOTURL}validate/orderitem/(?P[a-zA-Z0-9_-]+)$', views.OrderItemByTokenView.as_view(), name='orderitem_validate'), - path('admin/', admin.site.urls, name='admin'), - path('', include(router.urls)), - path('api/docs/schema', SpectacularAPIView.as_view(), name='schema'), - path('api/docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), - path('health/', include('health_check.urls')), - + path(f'{ROOTURL}admin/', admin.site.urls, name='admin'), + path(f'{ROOTURL}', include(router.urls)), + path(f'{ROOTURL}docs/schema', SpectacularAPIView.as_view(), name='schema'), + path(f'{ROOTURL}docs/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'), + path(f'{ROOTURL}health/', include('health_check.urls')), ] + static(settings.STATIC_URL,document_root=settings.STATIC_ROOT) + static(settings.MEDIA_URL,document_root=settings.MEDIA_ROOT) # OIDC urls if settings.OIDC_ENABLED: urlpatterns += [ - path("oidc/callback", OIDCCallbackClass.as_view(), name="oidc_authentication_callback"), - path("oidc/authenticate/", OIDCAuthenticateClass.as_view(), name="oidc_authentication_init"), - path("oidc/logout", OIDCLogoutView.as_view(), name="oidc_logout"), + path(f'{ROOTURL}oidc/callback', OIDCCallbackClass.as_view(), name='oidc_authentication_callback'), + path(f'{ROOTURL}oidc/authenticate/', OIDCAuthenticateClass.as_view(), name='oidc_authentication_init'), + path(f'{ROOTURL}oidc/logout', OIDCLogoutView.as_view(), name='oidc_logout'), ]