diff --git a/.gitignore b/.gitignore index f8c895c2..4b7c63da 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ projects/ mergin_db +logs *.log .DS_Store *.stackdump diff --git a/development.md b/development.md index d1731221..d754a576 100644 --- a/development.md +++ b/development.md @@ -67,7 +67,8 @@ If you want to run the whole stack locally, you can use the docker. Docker will docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d # Give ownership of the ./projects folder to user that is running the gunicorn container -sudo chown 901:999 projects/ +sudo chown 901:999 projects +sudo chown 101:999 logs # init db and create user docker exec -it merginmaps-server flask init-db diff --git a/docker-compose.yml b/docker-compose.yml index c697278b..3c4162bf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -95,5 +95,6 @@ services: volumes: - ./projects:/data # map data dir to host - ./nginx.conf:/etc/nginx/conf.d/default.conf + - ./logs:/var/log/nginx/ networks: - merginmaps diff --git a/nginx.conf b/nginx.conf index 598b8e3e..2371e8e4 100644 --- a/nginx.conf +++ b/nginx.conf @@ -10,6 +10,11 @@ server { # We are only proxying - not returning any files #root /dev/null; + # Logs - uncomment to enable logs written to file + # make sure mounted directory has correct permissions and beware of disk space used by the logs + # access_log /var/log/nginx/access.log; + # error_log /var/log/nginx/error.log warn; + location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; diff --git a/server/mergin/auth/models.py b/server/mergin/auth/models.py index b54336ce..85741470 100644 --- a/server/mergin/auth/models.py +++ b/server/mergin/auth/models.py @@ -203,8 +203,10 @@ def generate_username(cls, email: str) -> Optional[str]: ).ljust(4, "0") # additional check for reserved words username = f"{username}0" if is_reserved_word(username) else username + # some value until 25 + space for suffix + username = username[:22] # check if we already do not have existing usernames - suffix = db.session.execute( + query = db.session.execute( text( """ SELECT @@ -212,13 +214,14 @@ def generate_username(cls, email: str) -> Optional[str]: FROM "user" WHERE username = :username OR - username SIMILAR TO :username'\d+' + username SIMILAR TO :username_like ORDER BY replace(username, :username, '0')::int DESC LIMIT 1; """ ), - {"username": username}, - ).scalar() + {"username": username, "username_like": f"{username}\d+"}, + ) + suffix = query.scalar() return username if suffix is None else username + str(int(suffix) + 1) @classmethod diff --git a/server/mergin/tests/test_auth.py b/server/mergin/tests/test_auth.py index 167fe5f3..f70dbd53 100644 --- a/server/mergin/tests/test_auth.py +++ b/server/mergin/tests/test_auth.py @@ -117,6 +117,7 @@ def test_logout(client): 400, ), # tests with upper case, but email already exists (" mergin@mergin.com ", "#pwd123", 400), # invalid password + ("verylonglonglonglonglonglonglongemail@example.com", "#pwd1234", 201), ] @@ -851,6 +852,10 @@ def test_username_generation(client): user = add_user("support1", "user") assert User.generate_username("support@example.com") == "support0" + assert ( + User.generate_username("verylonglonglonglonglong@example.com") + == "verylonglonglonglonglo" + ) def test_server_usage(client): diff --git a/server/migrations/community/d02961c7416c_add_project_member_table.py b/server/migrations/community/d02961c7416c_add_project_member_table.py index fe13279b..3a04964e 100644 --- a/server/migrations/community/d02961c7416c_add_project_member_table.py +++ b/server/migrations/community/d02961c7416c_add_project_member_table.py @@ -173,7 +173,13 @@ def data_upgrade(): FROM project_access ) INSERT INTO project_member (project_id, user_id, role) - SELECT project_id, user_id, role::project_role FROM members; + SELECT m.project_id, m.user_id, m.role::project_role + FROM members m + LEFT OUTER JOIN "user" u on u.id = m.user_id + LEFT OUTER JOIN project p ON p.id = m.project_id + WHERE + u.username !~ 'deleted_\d{13}$' AND + p.removed_at IS NULL; """ ) ) diff --git a/ssl-proxy.conf b/ssl-proxy.conf new file mode 100644 index 00000000..e4746e24 --- /dev/null +++ b/ssl-proxy.conf @@ -0,0 +1,63 @@ + + server { + listen 80; + server_name merginmaps.company.com; # FIXME + + if ($scheme != "https") { + return 301 https://$host$request_uri; + } + } + + upstream app_server { + # route to the application proxy + server 127.0.0.1:8080 fail_timeout=0; + } + + server { + listen 443 ssl; + server_name merginmaps.company.com; # FIXME + client_max_body_size 4G; + + ssl_certificate_key /etc/letsencrypt/live/merginmaps.company.com/privkey.pem; # FIXME + ssl_certificate /etc/letsencrypt/live/merginmaps.company.com/fullchain.pem; # FIXME + + # Don't show version information + server_tokens off; + + # Enable gzip compression + gzip on; + gzip_min_length 10240; + gzip_comp_level 1; + gzip_vary on; + gzip_proxied any; + gzip_types + text/css + text/javascript + application/javascript + application/x-javascript; + + # Prevent crawlers from indexing and following links for all content served from the mergin app + add_header X-Robots-Tag "none"; + + # Protect against clickjacking iframe + add_header Content-Security-Policy "frame-ancestors 'self';" always; + + # Add a HSTS policy to prevent plain http from browser + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + + # Set cookies security flags + proxy_cookie_flags ~ secure httponly samesite=strict; + + location / { + root /var/www/html; + + # The lines below were copied from application proxy + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header Host $http_host; + # we don't want nginx trying to do something clever with + # redirects, we set the Host: header above already. + proxy_redirect off; + proxy_pass http://app_server; + } + }