From 84d1484728a5de022d809861c26d240c20279eee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-X=2E=20T=2E?= Date: Fri, 4 Sep 2026 21:25:16 -0400 Subject: [PATCH 1/4] feat(labrinth): account standing --- ...d7f634b673b5a65745d8d4f172b7de2478745.json | 15 + ...22c85d06f1ad9f6dce7781511332e18f3390.json} | 7 +- ...46c7d1a6d64f04592b3610673ff3bd76707e.json} | 4 +- ...6783dac56fb576672161dc66ddb44c059be1.json} | 10 +- ...0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json | 35 ++ ...90c3aebf59dfdbff277533e507ae3ef08c652.json | 29 -- ...71f2105b4596a9a391b9dd436ca9f86779df.json} | 4 +- ...cb85f234d858851232d9688e62f9530651e8.json} | 46 ++- .../20260904120000_account_standing.sql | 2 + apps/labrinth/src/auth/mod.rs | 8 +- apps/labrinth/src/auth/oauth/errors.rs | 4 + apps/labrinth/src/auth/oauth/mod.rs | 9 +- apps/labrinth/src/auth/templates/mod.rs | 2 +- apps/labrinth/src/auth/validate.rs | 41 +- .../src/database/models/oauth_token_item.rs | 16 +- .../src/database/models/passkey_item.rs | 30 +- .../labrinth/src/database/models/user_item.rs | 17 +- .../src/database/models/users_redeemals.rs | 4 + apps/labrinth/src/models/v2/user.rs | 5 +- apps/labrinth/src/models/v3/users.rs | 33 ++ apps/labrinth/src/routes/analytics.rs | 5 +- apps/labrinth/src/routes/internal/admin.rs | 2 + .../labrinth/src/routes/internal/affiliate.rs | 7 + .../src/routes/internal/analytics_event.rs | 4 + .../src/routes/internal/attribution.rs | 12 +- apps/labrinth/src/routes/internal/billing.rs | 14 +- .../src/routes/internal/delphi/mod.rs | 4 + .../routes/internal/external_notifications.rs | 3 +- apps/labrinth/src/routes/internal/flows.rs | 90 ++++- apps/labrinth/src/routes/internal/gdpr.rs | 3 +- apps/labrinth/src/routes/internal/medal.rs | 17 +- .../internal/moderation/external_license.rs | 7 + .../src/routes/internal/moderation/mod.rs | 16 +- .../routes/internal/moderation/tech_review.rs | 13 + .../internal/moderation/tech_review/global.rs | 3 + .../internal/moderation/tech_review/rules.rs | 7 + .../moderation/tech_review/rules_scan.rs | 4 + apps/labrinth/src/routes/internal/pats.rs | 6 +- apps/labrinth/src/routes/internal/privacy.rs | 7 +- .../src/routes/internal/server_ping.rs | 2 + apps/labrinth/src/routes/internal/session.rs | 7 +- apps/labrinth/src/routes/internal/statuses.rs | 2 + apps/labrinth/src/routes/maven.rs | 5 + apps/labrinth/src/routes/mod.rs | 30 +- apps/labrinth/src/routes/updates.rs | 3 +- apps/labrinth/src/routes/v2/users.rs | 4 +- .../src/routes/v3/analytics_get/facets/mod.rs | 2 + .../src/routes/v3/analytics_get/mod.rs | 2 + apps/labrinth/src/routes/v3/blocked_users.rs | 5 +- apps/labrinth/src/routes/v3/collections.rs | 10 +- apps/labrinth/src/routes/v3/content/mod.rs | 3 +- apps/labrinth/src/routes/v3/disclosures.rs | 4 +- apps/labrinth/src/routes/v3/friends.rs | 5 +- apps/labrinth/src/routes/v3/images.rs | 3 +- apps/labrinth/src/routes/v3/limits.rs | 4 + apps/labrinth/src/routes/v3/notifications.rs | 8 +- apps/labrinth/src/routes/v3/oauth_clients.rs | 9 + apps/labrinth/src/routes/v3/organizations.rs | 14 +- apps/labrinth/src/routes/v3/payouts.rs | 10 +- .../src/routes/v3/project_creation.rs | 4 +- .../src/routes/v3/project_creation/new.rs | 2 + apps/labrinth/src/routes/v3/projects.rs | 18 +- apps/labrinth/src/routes/v3/reports.rs | 10 +- apps/labrinth/src/routes/v3/teams.rs | 11 +- apps/labrinth/src/routes/v3/threads.rs | 6 +- apps/labrinth/src/routes/v3/users.rs | 122 +++++- .../src/routes/v3/version_creation.rs | 4 +- apps/labrinth/src/routes/v3/version_file.rs | 11 +- apps/labrinth/src/routes/v3/versions.rs | 8 +- apps/labrinth/tests/account_standing.rs | 358 ++++++++++++++++++ 70 files changed, 1067 insertions(+), 164 deletions(-) create mode 100644 apps/labrinth/.sqlx/query-211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745.json rename apps/labrinth/.sqlx/{query-a335ff4338fa4b3c1aaa9f62c61aad3c3e8bef0e68035a024995c62cfe69c568.json => query-44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390.json} (66%) rename apps/labrinth/.sqlx/{query-77d39c54d5ca1622b53f029cb4c13edaed963e65d9e8fb9c58c116bba24fe2ac.json => query-71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e.json} (56%) rename apps/labrinth/.sqlx/{query-05d26562a95715d65bbb2fd1c4163ebb067931f4c3caeb93601c98f1d533983b.json => query-8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1.json} (61%) create mode 100644 apps/labrinth/.sqlx/query-9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json delete mode 100644 apps/labrinth/.sqlx/query-a6422c468cf5121b44ceb33f7cf90c3aebf59dfdbff277533e507ae3ef08c652.json rename apps/labrinth/.sqlx/{query-6b881555e610ddc6796cdcbfd2de26e68b10522d0f1df3f006d58f6b72be9911.json => query-d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df.json} (63%) rename apps/labrinth/.sqlx/{query-3ca51012492b969bb6a474ee22c9e53e57b6da3a1145f0d86a2bab36be436eb6.json => query-f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8.json} (70%) create mode 100644 apps/labrinth/migrations/20260904120000_account_standing.sql create mode 100644 apps/labrinth/tests/account_standing.rs diff --git a/apps/labrinth/.sqlx/query-211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745.json b/apps/labrinth/.sqlx/query-211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745.json new file mode 100644 index 0000000000..aac3563f17 --- /dev/null +++ b/apps/labrinth/.sqlx/query-211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n\t\t\t\t\tUPDATE users SET account_standing = $1 WHERE id = $2\n\t\t\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Varchar", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745" +} diff --git a/apps/labrinth/.sqlx/query-a335ff4338fa4b3c1aaa9f62c61aad3c3e8bef0e68035a024995c62cfe69c568.json b/apps/labrinth/.sqlx/query-44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390.json similarity index 66% rename from apps/labrinth/.sqlx/query-a335ff4338fa4b3c1aaa9f62c61aad3c3e8bef0e68035a024995c62cfe69c568.json rename to apps/labrinth/.sqlx/query-44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390.json index bfc425a3b4..7d23935e09 100644 --- a/apps/labrinth/.sqlx/query-a335ff4338fa4b3c1aaa9f62c61aad3c3e8bef0e68035a024995c62cfe69c568.json +++ b/apps/labrinth/.sqlx/query-44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO users (\n id, username, email,\n avatar_url, raw_avatar_url, bio, created,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter,\n eligibility_verified_at\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9, $10, $11, $12, $13,\n $14, $15, $16, $17, $18, $19, $20, $21, $22,\n $23\n )\n ", + "query": "\n INSERT INTO users (\n id, username, email,\n avatar_url, raw_avatar_url, bio, created,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter,\n\t\t\t\teligibility_verified_at, account_standing\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9, $10, $11, $12, $13,\n $14, $15, $16, $17, $18, $19, $20, $21, $22,\n\t\t\t\t$23, $24\n )\n ", "describe": { "columns": [], "parameters": { @@ -27,10 +27,11 @@ "Text", "Bool", "Bool", - "Timestamptz" + "Timestamptz", + "Varchar" ] }, "nullable": [] }, - "hash": "a335ff4338fa4b3c1aaa9f62c61aad3c3e8bef0e68035a024995c62cfe69c568" + "hash": "44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390" } diff --git a/apps/labrinth/.sqlx/query-77d39c54d5ca1622b53f029cb4c13edaed963e65d9e8fb9c58c116bba24fe2ac.json b/apps/labrinth/.sqlx/query-71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e.json similarity index 56% rename from apps/labrinth/.sqlx/query-77d39c54d5ca1622b53f029cb4c13edaed963e65d9e8fb9c58c116bba24fe2ac.json rename to apps/labrinth/.sqlx/query-71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e.json index acc7285b88..001af3460b 100644 --- a/apps/labrinth/.sqlx/query-77d39c54d5ca1622b53f029cb4c13edaed963e65d9e8fb9c58c116bba24fe2ac.json +++ b/apps/labrinth/.sqlx/query-71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE users\n SET paypal_country = $1, paypal_email = $2, paypal_id = $3\n WHERE id = $4\n ", + "query": "\n UPDATE users\n SET paypal_country = $1, paypal_email = $2, paypal_id = $3\n\t\t\t\tWHERE id = $4 AND account_standing = 'full'\n ", "describe": { "columns": [], "parameters": { @@ -13,5 +13,5 @@ }, "nullable": [] }, - "hash": "77d39c54d5ca1622b53f029cb4c13edaed963e65d9e8fb9c58c116bba24fe2ac" + "hash": "71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e" } diff --git a/apps/labrinth/.sqlx/query-05d26562a95715d65bbb2fd1c4163ebb067931f4c3caeb93601c98f1d533983b.json b/apps/labrinth/.sqlx/query-8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1.json similarity index 61% rename from apps/labrinth/.sqlx/query-05d26562a95715d65bbb2fd1c4163ebb067931f4c3caeb93601c98f1d533983b.json rename to apps/labrinth/.sqlx/query-8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1.json index 106e54f7af..e3f04ac4c0 100644 --- a/apps/labrinth/.sqlx/query-05d26562a95715d65bbb2fd1c4163ebb067931f4c3caeb93601c98f1d533983b.json +++ b/apps/labrinth/.sqlx/query-8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, user_id, name, credential_id,\n passkey AS \"passkey: sqlx::types::Json\",\n last_used, created_at\n FROM user_passkeys\n WHERE credential_id = $1\n ", + "query": "\n\t\t\tSELECT user_passkeys.id, user_id, name, credential_id,\n passkey AS \"passkey: sqlx::types::Json\",\n\t\t\t\t last_used, created_at,\n\t\t\t\t users.account_standing AS \"account_standing: AccountStanding\"\n FROM user_passkeys\n\t\t\tJOIN users ON users.id = user_passkeys.user_id\n WHERE credential_id = $1\n ", "describe": { "columns": [ { @@ -37,6 +37,11 @@ "ordinal": 6, "name": "created_at", "type_info": "Timestamptz" + }, + { + "ordinal": 7, + "name": "account_standing: AccountStanding", + "type_info": "Varchar" } ], "parameters": { @@ -51,8 +56,9 @@ false, false, true, + false, false ] }, - "hash": "05d26562a95715d65bbb2fd1c4163ebb067931f4c3caeb93601c98f1d533983b" + "hash": "8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1" } diff --git a/apps/labrinth/.sqlx/query-9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json b/apps/labrinth/.sqlx/query-9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json new file mode 100644 index 0000000000..b2add6f5d5 --- /dev/null +++ b/apps/labrinth/.sqlx/query-9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n users.id,\n\t\t\t\tusers.account_standing AS \"account_standing: AccountStanding\",\n users_redeemals.status AS \"status: Option\"\n FROM\n users\n LEFT JOIN\n users_redeemals ON users_redeemals.user_id = users.id\n AND users_redeemals.offer = $2\n WHERE\n users.username = $1\n ORDER BY\n users_redeemals.redeemed DESC\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "account_standing: AccountStanding", + "type_info": "Varchar" + }, + { + "ordinal": 2, + "name": "status: Option", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c" +} diff --git a/apps/labrinth/.sqlx/query-a6422c468cf5121b44ceb33f7cf90c3aebf59dfdbff277533e507ae3ef08c652.json b/apps/labrinth/.sqlx/query-a6422c468cf5121b44ceb33f7cf90c3aebf59dfdbff277533e507ae3ef08c652.json deleted file mode 100644 index 4adb958c1f..0000000000 --- a/apps/labrinth/.sqlx/query-a6422c468cf5121b44ceb33f7cf90c3aebf59dfdbff277533e507ae3ef08c652.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n users.id,\n users_redeemals.status AS \"status: Option\"\n FROM\n users\n LEFT JOIN\n users_redeemals ON users_redeemals.user_id = users.id\n AND users_redeemals.offer = $2\n WHERE\n users.username = $1\n ORDER BY\n users_redeemals.redeemed DESC\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "status: Option", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - false - ] - }, - "hash": "a6422c468cf5121b44ceb33f7cf90c3aebf59dfdbff277533e507ae3ef08c652" -} diff --git a/apps/labrinth/.sqlx/query-6b881555e610ddc6796cdcbfd2de26e68b10522d0f1df3f006d58f6b72be9911.json b/apps/labrinth/.sqlx/query-d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df.json similarity index 63% rename from apps/labrinth/.sqlx/query-6b881555e610ddc6796cdcbfd2de26e68b10522d0f1df3f006d58f6b72be9911.json rename to apps/labrinth/.sqlx/query-d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df.json index 797214d3b2..f86e75699a 100644 --- a/apps/labrinth/.sqlx/query-6b881555e610ddc6796cdcbfd2de26e68b10522d0f1df3f006d58f6b72be9911.json +++ b/apps/labrinth/.sqlx/query-d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO oauth_access_tokens (\n id, authorization_id, token_hash, scopes, last_used\n )\n VALUES (\n $1, $2, $3, $4, $5\n )\n RETURNING created, expires\n ", + "query": "\n INSERT INTO oauth_access_tokens (\n id, authorization_id, token_hash, scopes, last_used\n )\n\t\t\tSELECT $1, $2, $3, $4, $5\n\t\t\tFROM oauth_client_authorizations auths\n\t\t\tJOIN users ON users.id = auths.user_id\n\t\t\tWHERE auths.id = $2 AND users.account_standing = 'full'\n RETURNING created, expires\n ", "describe": { "columns": [ { @@ -28,5 +28,5 @@ false ] }, - "hash": "6b881555e610ddc6796cdcbfd2de26e68b10522d0f1df3f006d58f6b72be9911" + "hash": "d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df" } diff --git a/apps/labrinth/.sqlx/query-3ca51012492b969bb6a474ee22c9e53e57b6da3a1145f0d86a2bab36be436eb6.json b/apps/labrinth/.sqlx/query-f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8.json similarity index 70% rename from apps/labrinth/.sqlx/query-3ca51012492b969bb6a474ee22c9e53e57b6da3a1145f0d86a2bab36be436eb6.json rename to apps/labrinth/.sqlx/query-f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8.json index 506d54fda7..f74c8fb9ef 100644 --- a/apps/labrinth/.sqlx/query-3ca51012492b969bb6a474ee22c9e53e57b6da3a1145f0d86a2bab36be436eb6.json +++ b/apps/labrinth/.sqlx/query-f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, email,\n avatar_url, raw_avatar_url, username, bio,\n created, role, badges,\n (\n SELECT MAX(campaign_donations.donated_at)\n FROM campaign_donations\n WHERE campaign_donations.user_id = users.id\n ) AS campaign_pride_26_last_donated_at,\n (\n SELECT SUM(campaign_donations.amount_usd)\n FROM campaign_donations\n WHERE campaign_donations.user_id = users.id\n ) AS campaign_pride_26_total_amount_donated_usd,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter,\n eligibility_verified_at\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n ", + "query": "\n SELECT id, email,\n avatar_url, raw_avatar_url, username, bio,\n\t\t\t\t\t\tcreated, role, badges, account_standing AS \"account_standing: AccountStanding\",\n (\n SELECT MAX(campaign_donations.donated_at)\n FROM campaign_donations\n WHERE campaign_donations.user_id = users.id\n ) AS campaign_pride_26_last_donated_at,\n (\n SELECT SUM(campaign_donations.amount_usd)\n FROM campaign_donations\n WHERE campaign_donations.user_id = users.id\n ) AS campaign_pride_26_total_amount_donated_usd,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter,\n eligibility_verified_at\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n\t\t\t\t\t", "describe": { "columns": [ { @@ -50,96 +50,101 @@ }, { "ordinal": 9, + "name": "account_standing: AccountStanding", + "type_info": "Varchar" + }, + { + "ordinal": 10, "name": "campaign_pride_26_last_donated_at", "type_info": "Timestamptz" }, { - "ordinal": 10, + "ordinal": 11, "name": "campaign_pride_26_total_amount_donated_usd", "type_info": "Numeric" }, { - "ordinal": 11, + "ordinal": 12, "name": "github_id", "type_info": "Int8" }, { - "ordinal": 12, + "ordinal": 13, "name": "discord_id", "type_info": "Int8" }, { - "ordinal": 13, + "ordinal": 14, "name": "gitlab_id", "type_info": "Int8" }, { - "ordinal": 14, + "ordinal": 15, "name": "google_id", "type_info": "Varchar" }, { - "ordinal": 15, + "ordinal": 16, "name": "steam_id", "type_info": "Int8" }, { - "ordinal": 16, + "ordinal": 17, "name": "microsoft_id", "type_info": "Varchar" }, { - "ordinal": 17, + "ordinal": 18, "name": "email_verified", "type_info": "Bool" }, { - "ordinal": 18, + "ordinal": 19, "name": "password", "type_info": "Text" }, { - "ordinal": 19, + "ordinal": 20, "name": "totp_secret", "type_info": "Varchar" }, { - "ordinal": 20, + "ordinal": 21, "name": "paypal_id", "type_info": "Text" }, { - "ordinal": 21, + "ordinal": 22, "name": "paypal_country", "type_info": "Text" }, { - "ordinal": 22, + "ordinal": 23, "name": "paypal_email", "type_info": "Text" }, { - "ordinal": 23, + "ordinal": 24, "name": "venmo_handle", "type_info": "Text" }, { - "ordinal": 24, + "ordinal": 25, "name": "stripe_customer_id", "type_info": "Text" }, { - "ordinal": 25, + "ordinal": 26, "name": "allow_friend_requests", "type_info": "Bool" }, { - "ordinal": 26, + "ordinal": 27, "name": "is_subscribed_to_newsletter", "type_info": "Bool" }, { - "ordinal": 27, + "ordinal": 28, "name": "eligibility_verified_at", "type_info": "Timestamptz" } @@ -160,6 +165,7 @@ false, false, false, + false, null, null, true, @@ -181,5 +187,5 @@ true ] }, - "hash": "3ca51012492b969bb6a474ee22c9e53e57b6da3a1145f0d86a2bab36be436eb6" + "hash": "f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8" } diff --git a/apps/labrinth/migrations/20260904120000_account_standing.sql b/apps/labrinth/migrations/20260904120000_account_standing.sql new file mode 100644 index 0000000000..9702c00faf --- /dev/null +++ b/apps/labrinth/migrations/20260904120000_account_standing.sql @@ -0,0 +1,2 @@ +ALTER TABLE users + ADD COLUMN account_standing varchar NOT NULL DEFAULT 'full'; diff --git a/apps/labrinth/src/auth/mod.rs b/apps/labrinth/src/auth/mod.rs index c58a032f13..de77655a37 100644 --- a/apps/labrinth/src/auth/mod.rs +++ b/apps/labrinth/src/auth/mod.rs @@ -9,8 +9,8 @@ pub use checks::{ }; use serde::{Deserialize, Serialize}; pub use validate::{ - check_is_moderator_from_headers, get_user_from_bearer_token, - get_user_from_headers, + StandingRequirement, check_is_moderator_from_headers, + get_user_from_bearer_token, get_user_from_headers, }; use crate::file_hosting::FileHostingError; @@ -39,6 +39,8 @@ pub enum AuthenticationError { Mail(#[from] crate::queue::email::MailError), #[error("Invalid Authentication Credentials")] InvalidCredentials, + #[error("account is locked")] + AccountLocked, #[error("Authentication method was not valid")] InvalidAuthMethod, #[error("GitHub Token from incorrect Client ID")] @@ -74,6 +76,7 @@ impl actix_web::ResponseError for AuthenticationError { StatusCode::INTERNAL_SERVER_ERROR } AuthenticationError::InvalidCredentials => StatusCode::UNAUTHORIZED, + AuthenticationError::AccountLocked => StatusCode::FORBIDDEN, AuthenticationError::Decoding(..) => StatusCode::BAD_REQUEST, AuthenticationError::Mail(..) => StatusCode::INTERNAL_SERVER_ERROR, AuthenticationError::InvalidAuthMethod => StatusCode::UNAUTHORIZED, @@ -109,6 +112,7 @@ impl AuthenticationError { AuthenticationError::SerDe(..) => "invalid_input", AuthenticationError::Reqwest(..) => "network_error", AuthenticationError::InvalidCredentials => "invalid_credentials", + AuthenticationError::AccountLocked => "account_locked", AuthenticationError::Decoding(..) => "decoding_error", AuthenticationError::Mail(..) => "mail_error", AuthenticationError::InvalidAuthMethod => "invalid_auth_method", diff --git a/apps/labrinth/src/auth/oauth/errors.rs b/apps/labrinth/src/auth/oauth/errors.rs index 09691afe84..d100134d5e 100644 --- a/apps/labrinth/src/auth/oauth/errors.rs +++ b/apps/labrinth/src/auth/oauth/errors.rs @@ -58,6 +58,9 @@ impl OAuthError { impl actix_web::ResponseError for OAuthError { fn status_code(&self) -> StatusCode { match *self.error_type { + OAuthErrorType::AuthenticationError( + AuthenticationError::AccountLocked, + ) if self.valid_redirect_uri.is_none() => StatusCode::FORBIDDEN, OAuthErrorType::AuthenticationError(_) | OAuthErrorType::FailedScopeParse(_) | OAuthErrorType::ScopesTooBroad @@ -174,6 +177,7 @@ impl OAuthErrorType { match self { Self::RedirectUriNotConfigured(_) | Self::ClientMissingRedirectURI { client_id: _ } => "invalid_uri", + Self::AuthenticationError(AuthenticationError::AccountLocked) => "access_denied", Self::AuthenticationError(_) | Self::InvalidAcceptFlowId => { "server_error" } diff --git a/apps/labrinth/src/auth/oauth/mod.rs b/apps/labrinth/src/auth/oauth/mod.rs index 083d0fabd6..1983234a04 100644 --- a/apps/labrinth/src/auth/oauth/mod.rs +++ b/apps/labrinth/src/auth/oauth/mod.rs @@ -1,8 +1,8 @@ use std::fmt::Write; -use crate::auth::get_user_from_headers; use crate::auth::oauth::uris::{OAuthRedirectUris, ValidatedRedirectUri}; use crate::auth::validate::extract_authorization_header; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::flow_item::DBFlow; use crate::database::models::oauth_client_authorization_item::DBOAuthClientAuthorization; @@ -83,6 +83,7 @@ pub async fn init_oauth( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await? .1; @@ -313,7 +314,10 @@ pub async fn request_token( user_id, } .insert(&mut transaction) - .await?; + .await? + .ok_or_else(|| { + OAuthError::error(OAuthErrorType::InvalidAuthCode) + })?; transaction.commit().await?; @@ -350,6 +354,7 @@ pub async fn accept_or_reject_client_scopes( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await? .1; diff --git a/apps/labrinth/src/auth/templates/mod.rs b/apps/labrinth/src/auth/templates/mod.rs index f4e3458784..a0a8b08c1a 100644 --- a/apps/labrinth/src/auth/templates/mod.rs +++ b/apps/labrinth/src/auth/templates/mod.rs @@ -40,7 +40,7 @@ impl Display for ErrorPage { impl ErrorPage { pub fn render(&self) -> HttpResponse { - HttpResponse::Ok() + HttpResponse::build(self.code) .append_header(("Content-Type", "text/html; charset=utf-8")) .body(self.to_string()) } diff --git a/apps/labrinth/src/auth/validate.rs b/apps/labrinth/src/auth/validate.rs index 46160e5048..9d20992a35 100644 --- a/apps/labrinth/src/auth/validate.rs +++ b/apps/labrinth/src/auth/validate.rs @@ -3,7 +3,7 @@ use crate::auth::AuthenticationError; use crate::database::models::{DBUser, user_item}; use crate::env::ENV; use crate::models::pats::Scopes; -use crate::models::users::User; +use crate::models::users::{AccountStanding, User}; use crate::queue::session::AuthQueue; use crate::routes::internal::session::get_session_metadata; use actix_web::HttpRequest; @@ -11,12 +11,37 @@ use actix_web::http::header::{AUTHORIZATION, HeaderValue}; use chrono::Utc; use xredis::RedisPool; +/// Required account standing, independent of token scopes and user role. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum StandingRequirement { + Full, + None, +} + +impl StandingRequirement { + pub fn check( + self, + standing: AccountStanding, + ) -> Result<(), AuthenticationError> { + match (self, standing) { + (Self::Full, AccountStanding::Locked) => { + Err(AuthenticationError::AccountLocked) + } + (Self::Full, AccountStanding::Full) + | (Self::None, AccountStanding::Full | AccountStanding::Locked) => { + Ok(()) + } + } + } +} + pub async fn get_maybe_user_from_headers<'a, E>( req: &HttpRequest, executor: E, redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, + standing_requirement: StandingRequirement, ) -> Result, AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -33,6 +58,7 @@ where redis, session_queue, false, + standing_requirement, ) .await? else { @@ -52,6 +78,7 @@ pub async fn get_full_user_from_headers<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, + standing_requirement: StandingRequirement, ) -> Result<(Scopes, DBUser), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -63,6 +90,7 @@ where redis, session_queue, false, + standing_requirement, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -81,6 +109,7 @@ pub async fn get_user_from_headers<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, + standing_requirement: StandingRequirement, ) -> Result<(Scopes, User), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -91,6 +120,7 @@ where redis, session_queue, required_scopes, + standing_requirement, ) .await?; @@ -104,6 +134,7 @@ pub async fn get_user_from_bearer_token<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, allow_expired: bool, + standing_requirement: StandingRequirement, ) -> Result<(Scopes, User), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -115,6 +146,7 @@ where redis, session_queue, allow_expired, + standing_requirement, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -129,6 +161,7 @@ pub async fn get_user_record_from_bearer_token<'a, 'b, E>( redis: &RedisPool, session_queue: &AuthQueue, allow_expired: bool, + standing_requirement: StandingRequirement, ) -> Result, AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -229,6 +262,10 @@ where _ => return Err(AuthenticationError::InvalidAuthMethod), }; + if let Some((_, user)) = &possible_user { + standing_requirement.check(user.account_standing)?; + } + Ok(possible_user) } @@ -254,6 +291,7 @@ pub async fn check_is_moderator_from_headers<'a, 'b, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, + standing_requirement: StandingRequirement, ) -> Result where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -264,6 +302,7 @@ where redis, session_queue, required_scopes, + standing_requirement, ) .await? .1; diff --git a/apps/labrinth/src/database/models/oauth_token_item.rs b/apps/labrinth/src/database/models/oauth_token_item.rs index b4191bff09..14abc14ad6 100644 --- a/apps/labrinth/src/database/models/oauth_token_item.rs +++ b/apps/labrinth/src/database/models/oauth_token_item.rs @@ -66,15 +66,16 @@ impl DBOAuthAccessToken { pub async fn insert( &self, exec: impl crate::database::Executor<'_, Database = sqlx::Postgres>, - ) -> Result { + ) -> Result, DatabaseError> { let r = sqlx::query!( " INSERT INTO oauth_access_tokens ( id, authorization_id, token_hash, scopes, last_used ) - VALUES ( - $1, $2, $3, $4, $5 - ) + SELECT $1, $2, $3, $4, $5 + FROM oauth_client_authorizations auths + JOIN users ON users.id = auths.user_id + WHERE auths.id = $2 AND users.account_standing = 'full' RETURNING created, expires ", self.id.0, @@ -83,13 +84,10 @@ impl DBOAuthAccessToken { self.scopes.to_postgres(), Option::>::None ) - .fetch_one(exec) + .fetch_optional(exec) .await?; - let (created, expires) = (r.created, r.expires); - let time_until_expiration = expires - created; - - Ok(time_until_expiration) + Ok(r.map(|r| r.expires - r.created)) } pub fn hash_token(token: &str) -> String { diff --git a/apps/labrinth/src/database/models/passkey_item.rs b/apps/labrinth/src/database/models/passkey_item.rs index 063e0724f4..dab1e0cc26 100644 --- a/apps/labrinth/src/database/models/passkey_item.rs +++ b/apps/labrinth/src/database/models/passkey_item.rs @@ -1,6 +1,7 @@ use super::ids::*; use crate::database::PgTransaction; use crate::database::models::DatabaseError; +use crate::models::users::AccountStanding; use chrono::{DateTime, Utc}; use futures::TryStreamExt; use serde::{Deserialize, Serialize}; @@ -49,30 +50,37 @@ impl DBPasskey { pub async fn get_by_credential_id<'a, E>( credential_id: &[u8], exec: E, - ) -> Result, DatabaseError> + ) -> Result, DatabaseError> where E: crate::database::Executor<'a, Database = sqlx::Postgres>, { let row = sqlx::query!( r#" - SELECT id, user_id, name, credential_id, + SELECT user_passkeys.id, user_id, name, credential_id, passkey AS "passkey: sqlx::types::Json", - last_used, created_at + last_used, created_at, + users.account_standing AS "account_standing: AccountStanding" FROM user_passkeys + JOIN users ON users.id = user_passkeys.user_id WHERE credential_id = $1 "#, credential_id, ) .fetch_optional(exec) .await? - .map(|x| DBPasskey { - id: DBPasskeyId(x.id), - user_id: DBUserId(x.user_id), - name: x.name, - credential_id: x.credential_id, - passkey: x.passkey.0, - created_at: x.created_at, - last_used: x.last_used, + .map(|x| { + ( + DBPasskey { + id: DBPasskeyId(x.id), + user_id: DBUserId(x.user_id), + name: x.name, + credential_id: x.credential_id, + passkey: x.passkey.0, + created_at: x.created_at, + last_used: x.last_used, + }, + x.account_standing, + ) }); Ok(row) diff --git a/apps/labrinth/src/database/models/user_item.rs b/apps/labrinth/src/database/models/user_item.rs index cb88f48a72..4baba946c8 100644 --- a/apps/labrinth/src/database/models/user_item.rs +++ b/apps/labrinth/src/database/models/user_item.rs @@ -8,7 +8,7 @@ use crate::database::{PgTransaction, models}; use crate::models::billing::ChargeStatus; use crate::models::projects::ProjectStatus; use crate::models::threads::MessageBody; -use crate::models::users::Badges; +use crate::models::users::{AccountStanding, Badges}; use crate::util::error::Context; use ariadne::ids::base62_impl::{parse_base62, to_base62}; use chrono::{DateTime, Utc}; @@ -20,7 +20,7 @@ use std::fmt::{Debug, Display}; use std::hash::Hash; use xredis::RedisPool; -const USERS_NAMESPACE: &str = "users:v4"; +const USERS_NAMESPACE: &str = "users:v5"; const USER_USERNAMES_NAMESPACE: &str = "users_usernames:v4"; const USERS_PROJECTS_NAMESPACE: &str = "users_projects:v4"; @@ -52,6 +52,7 @@ pub struct DBUser { pub bio: Option, pub created: DateTime, pub role: String, + pub account_standing: AccountStanding, pub badges: Badges, #[serde(default)] pub campaign_pride_26: Option, @@ -90,14 +91,14 @@ impl DBUser { github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id, email_verified, password, paypal_id, paypal_country, paypal_email, venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter, - eligibility_verified_at + eligibility_verified_at, account_standing ) VALUES ( $1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, - $23 + $23, $24 ) ", self.id as DBUserId, @@ -123,6 +124,7 @@ impl DBUser { self.allow_friend_requests, self.is_subscribed_to_newsletter, self.eligibility_verified_at, + self.account_standing.as_str(), ) .execute(&mut *transaction) .await?; @@ -202,10 +204,10 @@ impl DBUser { .collect::>(); let users = sqlx::query!( - " + r#" SELECT id, email, avatar_url, raw_avatar_url, username, bio, - created, role, badges, + created, role, badges, account_standing AS "account_standing: AccountStanding", ( SELECT MAX(campaign_donations.donated_at) FROM campaign_donations @@ -222,7 +224,7 @@ impl DBUser { eligibility_verified_at FROM users WHERE id = ANY($1) OR LOWER(username) = ANY($2) - ", + "#, &user_ids, &slugs, ) @@ -244,6 +246,7 @@ impl DBUser { bio: u.bio, created: u.created, role: u.role, + account_standing: u.account_standing, badges: Badges::from_bits(u.badges as u64).unwrap_or_default(), campaign_pride_26: u .campaign_pride_26_last_donated_at diff --git a/apps/labrinth/src/database/models/users_redeemals.rs b/apps/labrinth/src/database/models/users_redeemals.rs index b72654d173..38713cfb26 100644 --- a/apps/labrinth/src/database/models/users_redeemals.rs +++ b/apps/labrinth/src/database/models/users_redeemals.rs @@ -1,4 +1,5 @@ use crate::database::models::DBUserId; +use crate::models::users::AccountStanding; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::{query, query_scalar}; @@ -244,6 +245,7 @@ impl UserRedeemal { #[derive(Debug)] pub struct RedeemalLookupFields { pub user_id: DBUserId, + pub account_standing: AccountStanding, pub redeemal_status: Option, } @@ -267,6 +269,7 @@ impl RedeemalLookupFields { r#" SELECT users.id, + users.account_standing AS "account_standing: AccountStanding", users_redeemals.status AS "status: Option" FROM users @@ -290,6 +293,7 @@ impl RedeemalLookupFields { Ok(maybe_row.map(|row| RedeemalLookupFields { user_id: DBUserId(row.id), + account_standing: row.account_standing, redeemal_status: row .status .as_deref() diff --git a/apps/labrinth/src/models/v2/user.rs b/apps/labrinth/src/models/v2/user.rs index 35622abf8d..aaf711f9e0 100644 --- a/apps/labrinth/src/models/v2/user.rs +++ b/apps/labrinth/src/models/v2/user.rs @@ -1,6 +1,6 @@ use crate::{ auth::AuthProvider, - models::users::{Badges, Role, UserPayoutData}, + models::users::{AccountStanding, Badges, Role, UserPayoutData}, }; use ariadne::ids::UserId; use chrono::{DateTime, Utc}; @@ -15,6 +15,8 @@ pub struct LegacyUser { pub bio: Option, pub created: DateTime, pub role: Role, + #[serde(skip_serializing_if = "Option::is_none")] + pub account_standing: Option, pub badges: Badges, pub auth_providers: Option>, // this was changed in v3, but not changes ones we want to keep out of v2 @@ -39,6 +41,7 @@ impl From for LegacyUser { bio: data.bio, created: data.created, role: data.role, + account_standing: data.account_standing, badges: data.badges, payout_data: data.payout_data, auth_providers: data.auth_providers, diff --git a/apps/labrinth/src/models/v3/users.rs b/apps/labrinth/src/models/v3/users.rs index fe5de4c18d..8e82a88266 100644 --- a/apps/labrinth/src/models/v3/users.rs +++ b/apps/labrinth/src/models/v3/users.rs @@ -6,6 +6,35 @@ use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; +#[derive( + Debug, + Clone, + Copy, + Default, + PartialEq, + Eq, + Serialize, + Deserialize, + sqlx::Decode, + utoipa::ToSchema, +)] +#[serde(rename_all = "lowercase")] +#[sqlx(rename_all = "lowercase")] +pub enum AccountStanding { + #[default] + Full, + Locked, +} + +impl AccountStanding { + pub fn as_str(&self) -> &'static str { + match self { + Self::Full => "full", + Self::Locked => "locked", + } + } +} + pub const DELETED_USER: UserId = UserId(127155982985829); bitflags::bitflags! { @@ -55,6 +84,8 @@ pub struct User { pub bio: Option, pub created: DateTime, pub role: Role, + #[serde(skip_serializing_if = "Option::is_none")] + pub account_standing: Option, pub badges: Badges, pub campaigns: UserCampaigns, @@ -113,6 +144,7 @@ impl From for User { bio: data.bio, created: data.created, role: Role::from_string(&data.role), + account_standing: None, badges: data.badges, campaigns: UserCampaigns { pride_26: data.campaign_pride_26, @@ -177,6 +209,7 @@ impl User { bio: db_user.bio, created: db_user.created, role: Role::from_string(&db_user.role), + account_standing: Some(db_user.account_standing), badges: db_user.badges, campaigns: UserCampaigns { pride_26: db_user.campaign_pride_26, diff --git a/apps/labrinth/src/routes/analytics.rs b/apps/labrinth/src/routes/analytics.rs index 7330f80d59..a94a93dd6a 100644 --- a/apps/labrinth/src/routes/analytics.rs +++ b/apps/labrinth/src/routes/analytics.rs @@ -1,4 +1,4 @@ -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::DBProject; use crate::env::ENV; @@ -80,6 +80,7 @@ pub async fn page_view_ingest( &redis, &session_queue, Scopes::empty(), + StandingRequirement::None, ) .await .ok(); @@ -210,6 +211,7 @@ pub async fn playtime_ingest( &redis, &session_queue, Scopes::PERFORM_ANALYTICS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -296,6 +298,7 @@ pub async fn minecraft_server_play_ingest( &redis, &session_queue, Scopes::empty(), + StandingRequirement::None, ) .await .map(|(_, user)| user) diff --git a/apps/labrinth/src/routes/internal/admin.rs b/apps/labrinth/src/routes/internal/admin.rs index 0fa0196156..9f391c1819 100644 --- a/apps/labrinth/src/routes/internal/admin.rs +++ b/apps/labrinth/src/routes/internal/admin.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::auth::validate::get_user_record_from_bearer_token; use crate::database::PgPool; use crate::models::analytics::{Download, DownloadReason}; @@ -168,6 +169,7 @@ pub async fn count_download( &redis, &session_queue, false, + StandingRequirement::None, ) .await .ok() diff --git a/apps/labrinth/src/routes/internal/affiliate.rs b/apps/labrinth/src/routes/internal/affiliate.rs index 8716dffba1..74dcdbb687 100644 --- a/apps/labrinth/src/routes/internal/affiliate.rs +++ b/apps/labrinth/src/routes/internal/affiliate.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use std::{collections::HashMap, net::Ipv4Addr, sync::Arc}; use xredis::RedisPool; @@ -59,6 +60,7 @@ pub async fn ingest_click( &redis, &session_queue, Scopes::empty(), + StandingRequirement::None, ) .await .map(|(_, user)| user) @@ -158,6 +160,7 @@ pub async fn get_all( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -214,6 +217,7 @@ pub async fn create( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -299,6 +303,7 @@ pub async fn get( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -343,6 +348,7 @@ pub async fn delete( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -400,6 +406,7 @@ pub async fn patch( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/analytics_event.rs b/apps/labrinth/src/routes/internal/analytics_event.rs index c68fc7c0c2..a4a4361d9f 100644 --- a/apps/labrinth/src/routes/internal/analytics_event.rs +++ b/apps/labrinth/src/routes/internal/analytics_event.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use actix_web::{HttpRequest, delete, patch, post, web}; use chrono::{DateTime, Utc}; use eyre::eyre; @@ -55,6 +56,7 @@ pub async fn analytics_event_create( &redis, &session_queue, Scopes::empty(), + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -116,6 +118,7 @@ pub async fn analytics_event_edit( &redis, &session_queue, Scopes::empty(), + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -167,6 +170,7 @@ pub async fn analytics_event_delete( &redis, &session_queue, Scopes::empty(), + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/attribution.rs b/apps/labrinth/src/routes/internal/attribution.rs index 444065345c..8db81fcd24 100644 --- a/apps/labrinth/src/routes/internal/attribution.rs +++ b/apps/labrinth/src/routes/internal/attribution.rs @@ -4,7 +4,9 @@ use chrono::{DateTime, Utc}; use eyre::eyre; use serde::{Deserialize, Serialize}; -use crate::auth::{check_is_moderator_from_headers, get_user_from_headers}; +use crate::auth::{ + StandingRequirement, check_is_moderator_from_headers, get_user_from_headers, +}; use crate::database::PgPool; use crate::database::models::{ DBFileId, DBOrganization, DBProject, DBTeamMember, DBVersion, @@ -125,6 +127,7 @@ pub async fn scan( &redis, &session_queue, Scopes::VERSION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -240,6 +243,7 @@ async fn force_scan_file( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -323,6 +327,7 @@ pub async fn list( &redis, &session_queue, Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -656,6 +661,7 @@ pub async fn update_group( &redis, &session_queue, Scopes::VERSION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -744,6 +750,7 @@ pub async fn delete_groups( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("deleting database records for `delete_groups`")?; @@ -783,6 +790,7 @@ pub async fn delete_all_groups( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("deleting database records for `delete_all_groups`")?; @@ -903,6 +911,7 @@ pub async fn assign( &redis, &session_queue, Scopes::VERSION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1066,6 +1075,7 @@ pub async fn split( &redis, &session_queue, Scopes::VERSION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/billing.rs b/apps/labrinth/src/routes/internal/billing.rs index 5b4f971c96..241081bb3c 100644 --- a/apps/labrinth/src/routes/internal/billing.rs +++ b/apps/labrinth/src/routes/internal/billing.rs @@ -1,6 +1,6 @@ use self::payments::*; use self::update_subscriptions::*; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::models::charge_item::DBCharge; use crate::database::models::ids::DBUserSubscriptionId; use crate::database::models::notification_item::NotificationBuilder; @@ -139,6 +139,7 @@ pub async fn subscriptions( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -219,6 +220,7 @@ pub async fn refund_charge( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -510,6 +512,7 @@ pub async fn reprocess_charge_tax( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -711,6 +714,7 @@ pub async fn edit_subscription( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1209,6 +1213,7 @@ pub async fn user_customer( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1260,6 +1265,7 @@ pub async fn charges( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1326,6 +1332,7 @@ pub async fn add_payment_method_flow( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1388,6 +1395,7 @@ pub async fn edit_payment_method( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1462,6 +1470,7 @@ pub async fn remove_payment_method( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1563,6 +1572,7 @@ pub async fn payment_methods( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1740,6 +1750,7 @@ pub async fn initiate_payment( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2819,6 +2830,7 @@ pub async fn credit( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/delphi/mod.rs b/apps/labrinth/src/routes/internal/delphi/mod.rs index 87acfbef58..c01292ea8c 100644 --- a/apps/labrinth/src/routes/internal/delphi/mod.rs +++ b/apps/labrinth/src/routes/internal/delphi/mod.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::util::error::ApiContext as _; use std::{collections::HashMap, fmt::Write, time::Instant}; use xredis::RedisPool; @@ -334,6 +335,7 @@ pub async fn _run( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -360,6 +362,7 @@ pub async fn version( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -392,6 +395,7 @@ pub async fn issue_type_schema( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("deserializing HTTP response")?; diff --git a/apps/labrinth/src/routes/internal/external_notifications.rs b/apps/labrinth/src/routes/internal/external_notifications.rs index e43eae6c54..04e17e9c9c 100644 --- a/apps/labrinth/src/routes/internal/external_notifications.rs +++ b/apps/labrinth/src/routes/internal/external_notifications.rs @@ -2,7 +2,7 @@ use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::collections::HashMap; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::ids::{DBNotificationId, DBUserId}; use crate::database::models::notification_item::DBNotification; @@ -375,6 +375,7 @@ pub async fn send_custom_email( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/flows.rs b/apps/labrinth/src/routes/internal/flows.rs index fd22aaf6d4..7c6b89ad95 100644 --- a/apps/labrinth/src/routes/internal/flows.rs +++ b/apps/labrinth/src/routes/internal/flows.rs @@ -1,7 +1,10 @@ use crate::auth::validate::{ get_full_user_from_headers, get_user_record_from_bearer_token, }; -use crate::auth::{AuthProvider, AuthenticationError, get_user_from_headers}; +use crate::auth::{ + AuthProvider, AuthenticationError, StandingRequirement, + get_user_from_headers, +}; use crate::database::PgPool; use crate::database::PgTransaction; use crate::database::models::flow_item::DBFlow; @@ -14,7 +17,7 @@ use crate::models::error::ApiError as ApiErrorResponse; use crate::models::ids::PasskeyId; use crate::models::notifications::NotificationBody; use crate::models::pats::Scopes; -use crate::models::users::{Badges, Role}; +use crate::models::users::{AccountStanding, Badges, Role}; use crate::queue::email::EmailQueue; use crate::queue::session::AuthQueue; use crate::routes::ApiError; @@ -252,6 +255,7 @@ impl TempUser { bio: self.bio, created: Utc::now(), role: Role::Developer.to_string(), + account_standing: AccountStanding::Full, badges: Badges::default(), campaign_pride_26: None, allow_friend_requests: true, @@ -1104,10 +1108,9 @@ pub async fn init( &redis, &session_queue, false, + StandingRequirement::Full, ) - .await - .ok() - .flatten() + .await? .map(|(_scopes, user)| user.id) } else { None @@ -1145,6 +1148,7 @@ pub async fn init( &redis, &session_queue, false, + StandingRequirement::Full, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -1290,11 +1294,11 @@ pub async fn auth_callback( "attempting to link a PayPal account without being logged in", )?; - sqlx::query!( + let updated = sqlx::query!( " UPDATE users SET paypal_country = $1, paypal_email = $2, paypal_id = $3 - WHERE id = $4 + WHERE id = $4 AND account_standing = 'full' ", oauth_user.country, oauth_user.email, @@ -1305,6 +1309,10 @@ pub async fn auth_callback( .await .wrap_err("failed to update user PayPal info")?; + if updated.rows_affected() == 0 { + return Err(AuthenticationError::AccountLocked); + } + transaction .commit() .await @@ -1326,23 +1334,22 @@ pub async fn auth_callback( return Err(AuthenticationError::ProviderAlreadyLinked); } + let user = DBUser::get_id(id, &**client, &redis) + .await? + .ok_or(AuthenticationError::InvalidCredentials)?; + StandingRequirement::Full.check(user.account_standing)?; + provider .update_user_id(id, Some(&oauth_user.id), &mut transaction) .await?; - let user = - crate::database::models::DBUser::get_id(id, &**client, &redis) - .await?; - - if let Some(user) = user { - NotificationBuilder { - body: NotificationBody::AuthProviderAdded { - provider: provider.as_str().to_string(), - }, - } - .insert(user.id, &mut transaction, &redis) - .await?; + NotificationBuilder { + body: NotificationBody::AuthProviderAdded { + provider: provider.as_str().to_string(), + }, } + .insert(user.id, &mut transaction, &redis) + .await?; transaction.commit().await?; crate::database::models::DBUser::clear_caches( @@ -1361,6 +1368,8 @@ pub async fn auth_callback( .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; + StandingRequirement::Full.check(user.account_standing)?; + if user.totp_secret.is_some() { let flow = DBFlow::Login2FA { user_id: user.id } .insert(Duration::minutes(30), &redis) @@ -1652,6 +1661,7 @@ pub async fn discord_community_link( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1724,6 +1734,7 @@ pub async fn delete_auth_provider( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2096,6 +2107,7 @@ impl ReadyAccountRegisterFlow { bio: None, created: Utc::now(), role: Role::Developer.to_string(), + account_standing: AccountStanding::Full, badges: Badges::default(), campaign_pride_26: None, allow_friend_requests: true, @@ -2312,6 +2324,10 @@ pub async fn login_password( .map_err(|_| AuthenticationError::InvalidCredentials) .wrap_auth_err("authenticating API request")?; + StandingRequirement::Full + .check(user.account_standing) + .wrap_auth_err("checking account standing")?; + if user.totp_secret.is_some() { let flow = DBFlow::Login2FA { user_id: user.id } .insert(Duration::minutes(30), &redis) @@ -2453,6 +2469,10 @@ pub async fn login_2fa( .ok_or_else(|| AuthenticationError::InvalidCredentials) .wrap_auth_err("fetching user from database")?; + StandingRequirement::Full + .check(user.account_standing) + .wrap_auth_err("checking account standing")?; + let mut transaction = pool .begin() .await @@ -2522,6 +2542,7 @@ pub async fn begin_2fa_flow( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2583,6 +2604,7 @@ pub async fn finish_2fa_flow( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2731,6 +2753,7 @@ pub async fn remove_2fa( &redis, &session_queue, false, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2904,9 +2927,14 @@ pub async fn reset_password_begin( if let Some(DBUser { id: user_id, email: user_email, + account_standing, .. }) = user { + StandingRequirement::Full + .check(account_standing) + .wrap_auth_err("checking account standing")?; + let flow = DBFlow::ForgotPassword { user_id } .insert(Duration::hours(24), &redis) .await @@ -2996,6 +3024,7 @@ pub async fn change_password( &redis, &session_queue, false, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3030,6 +3059,10 @@ pub async fn change_password( user }; + StandingRequirement::Full + .check(user.account_standing) + .wrap_auth_err("checking account standing")?; + let mut transaction = pool .begin() .await @@ -3177,6 +3210,7 @@ pub async fn set_email( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3307,6 +3341,7 @@ pub async fn resend_verify_email( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3401,6 +3436,10 @@ pub async fn verify_email( .ok_or_else(|| AuthenticationError::InvalidCredentials) .wrap_auth_err("fetching user from database")?; + StandingRequirement::Full + .check(user.account_standing) + .wrap_auth_err("checking account standing")?; + if user.email != Some(confirm_email) { return Err(ApiError::Request(eyre::eyre!( "E-mail does not match verify email. Try re-requesting the verification link.", @@ -3471,6 +3510,7 @@ pub async fn subscribe_newsletter( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3523,6 +3563,7 @@ pub async fn get_newsletter_subscription_status( &redis, &session_queue, Scopes::USER_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3577,6 +3618,7 @@ pub async fn register_passkey_start( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3680,6 +3722,7 @@ pub async fn register_passkey_finish( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3841,12 +3884,16 @@ pub async fn authenticate_passkey_finish( if let Some(DBFlow::AuthenticatePasskey { state }) = flow { let credential_id = response.credential.get_credential_id(); - let db_passkey = + let (db_passkey, account_standing) = DBPasskey::get_by_credential_id(credential_id, &**pool) .await .wrap_internal_err("failed to fetch passkey")? .wrap_request_err_with(|| "passkey not found")?; + StandingRequirement::Full + .check(account_standing) + .wrap_auth_err("checking account standing")?; + let mut transaction = pool .begin() .await @@ -3962,6 +4009,7 @@ pub async fn list_passkeys( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -4016,6 +4064,7 @@ pub async fn rename_passkey( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -4076,6 +4125,7 @@ pub async fn delete_passkey( &redis, &session_queue, Scopes::USER_AUTH_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/gdpr.rs b/apps/labrinth/src/routes/internal/gdpr.rs index 479209bd17..ef1ef5f0e5 100644 --- a/apps/labrinth/src/routes/internal/gdpr.rs +++ b/apps/labrinth/src/routes/internal/gdpr.rs @@ -1,4 +1,4 @@ -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::models::pats::Scopes; use crate::queue::session::AuthQueue; @@ -30,6 +30,7 @@ pub async fn export( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/medal.rs b/apps/labrinth/src/routes/internal/medal.rs index c443e19e75..8a960b523d 100644 --- a/apps/labrinth/src/routes/internal/medal.rs +++ b/apps/labrinth/src/routes/internal/medal.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::database::PgPool; use crate::util::error::Context as _; use actix_web::{HttpResponse, post, web}; @@ -51,10 +52,15 @@ pub async fn verify( match maybe_fields { None => Err(ApiError::NotFound(eyre::eyre!("resource not found"))), - Some(fields) => Ok(HttpResponse::Ok().json(VerifyResponse { - user_id: fields.user_id.into(), - redeemed: fields.redeemal_status.is_some(), - })), + Some(fields) => { + StandingRequirement::Full + .check(fields.account_standing) + .wrap_auth_err("checking account standing")?; + Ok(HttpResponse::Ok().json(VerifyResponse { + user_id: fields.user_id.into(), + redeemed: fields.redeemal_status.is_some(), + })) + } } } @@ -92,6 +98,9 @@ pub async fn redeem( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); } Some(fields) => { + StandingRequirement::Full + .check(fields.account_standing) + .wrap_auth_err("checking account standing")?; if fields.redeemal_status.is_some() { return Err(ApiError::Conflict(eyre::eyre!( "User already redeemed this offer", diff --git a/apps/labrinth/src/routes/internal/moderation/external_license.rs b/apps/labrinth/src/routes/internal/moderation/external_license.rs index 84ae825b04..415488b14a 100644 --- a/apps/labrinth/src/routes/internal/moderation/external_license.rs +++ b/apps/labrinth/src/routes/internal/moderation/external_license.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::collections::HashMap; @@ -357,6 +358,7 @@ pub async fn search( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating external license search")?; @@ -430,6 +432,7 @@ pub async fn lookup( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -470,6 +473,7 @@ pub async fn get_by_sha1( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -507,6 +511,7 @@ pub async fn get_by_sha1_bulk( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -567,6 +572,7 @@ async fn upsert_file_license( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -680,6 +686,7 @@ pub async fn update_license( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/mod.rs b/apps/labrinth/src/routes/internal/moderation/mod.rs index d6f3871793..247f0673f6 100644 --- a/apps/labrinth/src/routes/internal/moderation/mod.rs +++ b/apps/labrinth/src/routes/internal/moderation/mod.rs @@ -1,5 +1,5 @@ use super::ApiError; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::database::models::moderation_external_item; @@ -263,6 +263,7 @@ pub async fn get_projects_internal( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -747,6 +748,7 @@ pub async fn get_project_ids( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1096,6 +1098,7 @@ pub async fn get_project_meta( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1259,6 +1262,7 @@ pub async fn set_project_meta( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1365,6 +1369,7 @@ pub async fn acquire_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1429,6 +1434,7 @@ pub async fn override_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1480,6 +1486,7 @@ pub async fn get_lock_status( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1547,6 +1554,7 @@ pub async fn release_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1612,6 +1620,7 @@ pub async fn release_lock_beacon( &redis, &session_queue, false, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1668,6 +1677,7 @@ pub async fn delete_all_locks( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1709,6 +1719,7 @@ pub async fn get_user_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1753,6 +1764,7 @@ pub async fn get_users_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1810,6 +1822,7 @@ pub async fn get_organization_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1858,6 +1871,7 @@ pub async fn get_organizations_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review.rs b/apps/labrinth/src/routes/internal/moderation/tech_review.rs index 017a417944..b9e0b2f744 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::util::error::ApiContext as _; use std::{collections::HashMap, fmt}; use xredis::RedisPool; @@ -244,6 +245,7 @@ pub async fn get_issue( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -311,6 +313,7 @@ pub async fn get_report( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -761,6 +764,7 @@ pub async fn search_projects( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -993,6 +997,7 @@ pub async fn get_project_report( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1096,6 +1101,7 @@ pub async fn submit_report( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1303,6 +1309,7 @@ pub async fn update_issue_details( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("updating database records for `update_issue_details`")?; @@ -1456,6 +1463,7 @@ pub async fn update_global_issue_details( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err( @@ -1595,6 +1603,7 @@ pub async fn add_report( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("inserting database records for `add_report`")?; @@ -1679,6 +1688,7 @@ pub async fn get_user_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1719,6 +1729,7 @@ pub async fn get_users_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1774,6 +1785,7 @@ pub async fn get_organization_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -1815,6 +1827,7 @@ pub async fn get_organizations_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs index 3ca452aea9..f3df78ea2b 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use actix_web::{HttpRequest, post, web}; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -147,6 +148,7 @@ pub async fn search_global_issue_details( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating global issue search")?; @@ -371,6 +373,7 @@ pub async fn get_global_issue_detail( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating global issue detail request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs index 0848065045..eeb059133b 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use actix_web::{HttpRequest, delete, get, post, put, web}; use chrono::{DateTime, Utc}; use eyre::eyre; @@ -193,6 +194,7 @@ pub async fn test_rule( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -249,6 +251,7 @@ pub async fn get_rules( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -426,6 +429,7 @@ pub async fn get_rule_affected_details( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -536,6 +540,7 @@ pub async fn create_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -624,6 +629,7 @@ pub async fn update_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -707,6 +713,7 @@ pub async fn delete_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs index facbe622ce..020cd0f88c 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use std::collections::{BTreeMap, HashMap}; use actix_web::{HttpRequest, HttpResponse, get, post, web}; @@ -161,6 +162,7 @@ pub async fn get_rule_schema( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -206,6 +208,7 @@ pub async fn get_detail_rule_input( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -354,6 +357,7 @@ pub async fn scan_rules( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/pats.rs b/apps/labrinth/src/routes/internal/pats.rs index 63e4f10eba..de71b541e0 100644 --- a/apps/labrinth/src/routes/internal/pats.rs +++ b/apps/labrinth/src/routes/internal/pats.rs @@ -2,7 +2,7 @@ use crate::database; use crate::database::models::generate_pat_id; use crate::util::error::Context as _; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::routes::ApiError; use actix_web::web::{self, Data}; @@ -53,6 +53,7 @@ pub async fn get_pats( &redis, &session_queue, Scopes::PAT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -129,6 +130,7 @@ pub async fn create_pat( &redis, &session_queue, Scopes::PAT_CREATE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -240,6 +242,7 @@ pub async fn edit_pat( &redis, &session_queue, Scopes::PAT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -358,6 +361,7 @@ pub async fn delete_pat( &redis, &session_queue, Scopes::PAT_DELETE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/privacy.rs b/apps/labrinth/src/routes/internal/privacy.rs index 27a2cbd984..79836f3fdd 100644 --- a/apps/labrinth/src/routes/internal/privacy.rs +++ b/apps/labrinth/src/routes/internal/privacy.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::database::PgPool; use crate::database::models::blocked_user_item::DBBlockedUser; use crate::database::models::friend_item::DBFriend; @@ -49,7 +50,11 @@ pub async fn invite_privacy_status( ) .wrap_internal_err("resolving user ids")?; - let user_id = user.wrap_not_found_err("user not found")?.id; + let user = user.wrap_not_found_err("user not found")?; + StandingRequirement::Full + .check(user.account_standing) + .wrap_auth_err("checking inviter account standing")?; + let user_id = user.id; let target_id = target.wrap_not_found_err("target not found")?.id; let blocked = DBBlockedUser::is_blocked(target_id, user_id, &**pool) diff --git a/apps/labrinth/src/routes/internal/server_ping.rs b/apps/labrinth/src/routes/internal/server_ping.rs index 5847572ea5..ba75010315 100644 --- a/apps/labrinth/src/routes/internal/server_ping.rs +++ b/apps/labrinth/src/routes/internal/server_ping.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use std::time::Duration; use xredis::RedisPool; @@ -43,6 +44,7 @@ pub async fn ping_minecraft_java( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/session.rs b/apps/labrinth/src/routes/internal/session.rs index f7bd6f685c..b18aaa172f 100644 --- a/apps/labrinth/src/routes/internal/session.rs +++ b/apps/labrinth/src/routes/internal/session.rs @@ -1,5 +1,7 @@ use crate::auth::validate::get_user_from_bearer_token; -use crate::auth::{AuthenticationError, get_user_from_headers}; +use crate::auth::{ + AuthenticationError, StandingRequirement, get_user_from_headers, +}; use crate::database::models::DBUserId; use crate::database::models::session_item::DBSession; use crate::database::models::session_item::SessionBuilder; @@ -159,6 +161,7 @@ pub async fn list( &redis, &session_queue, Scopes::SESSION_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -215,6 +218,7 @@ pub async fn delete( &redis, &session_queue, Scopes::SESSION_DELETE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -292,6 +296,7 @@ pub async fn refresh( &redis, &session_queue, true, // Allow expired sessions, since we want to allow refreshing expired sessions + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/statuses.rs b/apps/labrinth/src/routes/internal/statuses.rs index 03bd1a9e1c..21cd330445 100644 --- a/apps/labrinth/src/routes/internal/statuses.rs +++ b/apps/labrinth/src/routes/internal/statuses.rs @@ -1,4 +1,5 @@ use crate::auth::AuthenticationError; +use crate::auth::StandingRequirement; use crate::auth::validate::get_user_record_from_bearer_token; use crate::database::models::friend_item::DBFriend; use crate::database::models::notification_item::DBNotification; @@ -68,6 +69,7 @@ pub async fn ws_init( &redis, &session_queue, false, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/maven.rs b/apps/labrinth/src/routes/maven.rs index 369eaf6d1f..4feecbd54b 100644 --- a/apps/labrinth/src/routes/maven.rs +++ b/apps/labrinth/src/routes/maven.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::auth::checks::{is_visible_project, is_visible_version}; use crate::database::PgPool; use crate::database::models::legacy_loader_fields::MinecraftGameVersion; @@ -100,6 +101,7 @@ pub async fn maven_metadata( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -338,6 +340,7 @@ pub async fn version_file( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -425,6 +428,7 @@ pub async fn version_file_sha1( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -491,6 +495,7 @@ pub async fn version_file_sha512( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/mod.rs b/apps/labrinth/src/routes/mod.rs index 3713066342..6ed9c9b084 100644 --- a/apps/labrinth/src/routes/mod.rs +++ b/apps/labrinth/src/routes/mod.rs @@ -1,3 +1,4 @@ +use crate::auth::AuthenticationError; use crate::env::ENV; use crate::util::cors::default_cors; use actix_cors::Cors; @@ -275,7 +276,14 @@ impl actix_web::ResponseError for ApiError { match self { Self::Internal(..) => StatusCode::INTERNAL_SERVER_ERROR, Self::Request(..) => StatusCode::BAD_REQUEST, - Self::Auth(..) => StatusCode::UNAUTHORIZED, + Self::Auth(report) => { + match report.downcast_ref::() { + Some(AuthenticationError::AccountLocked) => { + StatusCode::FORBIDDEN + } + _ => StatusCode::UNAUTHORIZED, + } + } Self::NotFound(..) => StatusCode::NOT_FOUND, Self::Conflict(..) => StatusCode::CONFLICT, Self::FailedDependency(..) => StatusCode::FAILED_DEPENDENCY, @@ -295,6 +303,26 @@ mod tests { use super::ApiError; #[test] + fn account_locked_preserves_forbidden_status_through_auth_context() { + use crate::{ + auth::{AuthenticationError, templates::ErrorPage}, + util::error::Context, + }; + use actix_web::{ResponseError, http::StatusCode}; + + let direct = AuthenticationError::AccountLocked; + assert_eq!(direct.status_code(), StatusCode::FORBIDDEN); + let wrapped = Err::<(), _>(direct) + .wrap_auth_err("checking account standing") + .unwrap_err() + .wrap_err("authenticating API request"); + assert_eq!(wrapped.status_code(), StatusCode::FORBIDDEN); + assert_eq!(wrapped.as_api_error().error, "auth_error"); + let page = ErrorPage::from(AuthenticationError::AccountLocked); + assert_eq!(page.error_response().status(), StatusCode::FORBIDDEN); + } + + #[test] fn api_error_serializes_source_chain_as_details() { let error = ApiError::Request( eyre::eyre!("root cause") diff --git a/apps/labrinth/src/routes/updates.rs b/apps/labrinth/src/routes/updates.rs index 95db80199d..5dc51d11e7 100644 --- a/apps/labrinth/src/routes/updates.rs +++ b/apps/labrinth/src/routes/updates.rs @@ -9,7 +9,7 @@ use actix_web::{HttpRequest, HttpResponse, get, web}; use serde::{Deserialize, Serialize}; use crate::auth::checks::{filter_visible_versions, is_visible_project}; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database; use crate::database::models::legacy_loader_fields::MinecraftGameVersion; use crate::models::pats::Scopes; @@ -67,6 +67,7 @@ pub async fn forge_updates( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/v2/users.rs b/apps/labrinth/src/routes/v2/users.rs index 860159d638..41c37541e6 100644 --- a/apps/labrinth/src/routes/v2/users.rs +++ b/apps/labrinth/src/routes/v2/users.rs @@ -2,7 +2,7 @@ use crate::database::PgPool; use crate::file_hosting::FileHost; use crate::models::notifications::Notification; use crate::models::projects::Project; -use crate::models::users::{Badges, Role, User}; +use crate::models::users::{AccountStanding, Badges, Role, User}; use crate::models::v2::notifications::LegacyNotification; use crate::models::v2::projects::LegacyProject; use crate::models::v2::user::LegacyUser; @@ -223,6 +223,7 @@ pub struct EditUser { #[validate(length(max = 160))] pub bio: Option>, pub role: Option, + pub account_standing: Option, pub badges: Option, pub allow_friend_requests: Option, } @@ -268,6 +269,7 @@ pub async fn user_edit( username: new_user.username, bio: new_user.bio, role: new_user.role, + account_standing: new_user.account_standing, badges: new_user.badges, venmo_handle: None, allow_friend_requests: new_user.allow_friend_requests, diff --git a/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs b/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs index 4fd0c498e3..8f04c09a58 100644 --- a/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs +++ b/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use xredis::RedisPool; @@ -79,6 +80,7 @@ pub async fn fetch_facets( &redis, &session_queue, Scopes::ANALYTICS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/analytics_get/mod.rs b/apps/labrinth/src/routes/v3/analytics_get/mod.rs index 71679c1e54..b6c0dd17db 100644 --- a/apps/labrinth/src/routes/v3/analytics_get/mod.rs +++ b/apps/labrinth/src/routes/v3/analytics_get/mod.rs @@ -7,6 +7,7 @@ //! requests, you have to zip together M arrays of N elements //! - this makes it inconvenient to have separate endpoints +use crate::auth::StandingRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; @@ -195,6 +196,7 @@ pub async fn fetch_analytics( &redis, &session_queue, Scopes::ANALYTICS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/blocked_users.rs b/apps/labrinth/src/routes/v3/blocked_users.rs index 2cdd00bbc9..267f53610b 100644 --- a/apps/labrinth/src/routes/v3/blocked_users.rs +++ b/apps/labrinth/src/routes/v3/blocked_users.rs @@ -1,4 +1,4 @@ -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::DBUser; use crate::database::models::blocked_user_item::DBBlockedUser; @@ -34,6 +34,7 @@ pub async fn block_user( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -92,6 +93,7 @@ pub async fn unblock_user( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -127,6 +129,7 @@ pub async fn get_blocked_users( &redis, &session_queue, Scopes::USER_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/collections.rs b/apps/labrinth/src/routes/v3/collections.rs index b5e5d266a8..d8af1e8bbc 100644 --- a/apps/labrinth/src/routes/v3/collections.rs +++ b/apps/labrinth/src/routes/v3/collections.rs @@ -1,6 +1,7 @@ use crate::auth::checks::is_visible_collection; use crate::auth::{ - filter_visible_collections, get_user_from_headers, require_verified_email, + StandingRequirement, filter_visible_collections, get_user_from_headers, + require_verified_email, }; use crate::database::PgPool; use crate::database::models::{ @@ -75,6 +76,7 @@ pub async fn collection_create( &redis, &session_queue, Scopes::COLLECTION_CREATE, + StandingRequirement::Full, ) .await? .1; @@ -179,6 +181,7 @@ pub async fn collections_get( &redis, &session_queue, Scopes::COLLECTION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -216,6 +219,7 @@ pub async fn collection_get( &redis, &session_queue, Scopes::COLLECTION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -267,6 +271,7 @@ pub async fn collection_edit( &redis, &session_queue, Scopes::COLLECTION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -450,6 +455,7 @@ pub async fn collection_icon_edit( &redis, &session_queue, Scopes::COLLECTION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -548,6 +554,7 @@ pub async fn delete_collection_icon( &redis, &session_queue, Scopes::COLLECTION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -619,6 +626,7 @@ pub async fn collection_delete( &redis, &session_queue, Scopes::COLLECTION_DELETE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/content/mod.rs b/apps/labrinth/src/routes/v3/content/mod.rs index 51379b7f1b..6534a2327f 100644 --- a/apps/labrinth/src/routes/v3/content/mod.rs +++ b/apps/labrinth/src/routes/v3/content/mod.rs @@ -2,7 +2,7 @@ use super::ApiError; use crate::auth::checks::{ filter_visible_versions, is_visible_project, is_visible_version, }; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::models::ids::DBVersionId; use crate::database::models::version_item::VersionQueryResult; use crate::database::models::{DBProject, DBVersion}; @@ -54,6 +54,7 @@ pub async fn resolve_content( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/v3/disclosures.rs b/apps/labrinth/src/routes/v3/disclosures.rs index b81fefdd32..c9c49f5405 100644 --- a/apps/labrinth/src/routes/v3/disclosures.rs +++ b/apps/labrinth/src/routes/v3/disclosures.rs @@ -6,7 +6,7 @@ use utoipa::ToSchema; use xredis::RedisPool; use crate::auth::checks::{is_team_member_project, is_visible_project}; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::{DBProject, models as db_models}; use crate::database::{PgPool, ReadOnlyPgPool}; use crate::models::disclosures::{ @@ -58,6 +58,7 @@ pub async fn get_project_disclosures( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|(_, user)| user) @@ -140,6 +141,7 @@ pub async fn modify_project_disclosures( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/friends.rs b/apps/labrinth/src/routes/v3/friends.rs index 5be589a6ac..eac0665a42 100644 --- a/apps/labrinth/src/routes/v3/friends.rs +++ b/apps/labrinth/src/routes/v3/friends.rs @@ -1,4 +1,4 @@ -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::blocked_user_item::DBBlockedUser; use crate::database::models::friend_item::DBFriend; @@ -43,6 +43,7 @@ pub async fn add_friend( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -193,6 +194,7 @@ pub async fn remove_friend( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -247,6 +249,7 @@ pub async fn friends( &redis, &session_queue, Scopes::USER_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/images.rs b/apps/labrinth/src/routes/v3/images.rs index 7e45da50c1..f2aea21557 100644 --- a/apps/labrinth/src/routes/v3/images.rs +++ b/apps/labrinth/src/routes/v3/images.rs @@ -1,6 +1,6 @@ use super::threads::is_authorized_thread; use crate::auth::checks::{is_team_member_project, is_team_member_version}; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::database::models::{ @@ -69,6 +69,7 @@ pub async fn images_add( &redis, &session_queue, context.relevant_scope(), + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/limits.rs b/apps/labrinth/src/routes/v3/limits.rs index ab11d37a8e..47977fc410 100644 --- a/apps/labrinth/src/routes/v3/limits.rs +++ b/apps/labrinth/src/routes/v3/limits.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::database::PgPool; use crate::util::error::Context as _; use crate::{ @@ -29,6 +30,7 @@ pub async fn get_project_limits( &redis, &session_queue, Scopes::empty(), + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -53,6 +55,7 @@ pub async fn get_organization_limits( &redis, &session_queue, Scopes::empty(), + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -77,6 +80,7 @@ pub async fn get_collection_limits( &redis, &session_queue, Scopes::empty(), + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/notifications.rs b/apps/labrinth/src/routes/v3/notifications.rs index 90ca9829b4..63291704ed 100644 --- a/apps/labrinth/src/routes/v3/notifications.rs +++ b/apps/labrinth/src/routes/v3/notifications.rs @@ -1,4 +1,4 @@ -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::models::ids::NotificationId; @@ -54,6 +54,7 @@ pub async fn notifications_get( &redis, &session_queue, Scopes::NOTIFICATION_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -111,6 +112,7 @@ pub async fn notification_get( &redis, &session_queue, Scopes::NOTIFICATION_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -162,6 +164,7 @@ pub async fn notification_read( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -233,6 +236,7 @@ pub async fn notification_delete( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -308,6 +312,7 @@ pub async fn notifications_read( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -387,6 +392,7 @@ pub async fn notifications_delete( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/oauth_clients.rs b/apps/labrinth/src/routes/v3/oauth_clients.rs index 039045f28d..8b5b22d905 100644 --- a/apps/labrinth/src/routes/v3/oauth_clients.rs +++ b/apps/labrinth/src/routes/v3/oauth_clients.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::{collections::HashSet, fmt::Display}; @@ -72,6 +73,7 @@ pub async fn get_user_clients( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -199,6 +201,7 @@ pub async fn oauth_client_create( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await? .1; @@ -267,6 +270,7 @@ pub async fn oauth_client_delete( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -337,6 +341,7 @@ pub async fn oauth_client_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -442,6 +447,7 @@ pub async fn oauth_client_icon_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -532,6 +538,7 @@ pub async fn oauth_client_icon_delete( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -598,6 +605,7 @@ pub async fn get_user_oauth_authorizations( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -637,6 +645,7 @@ pub async fn revoke_oauth_authorization( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/organizations.rs b/apps/labrinth/src/routes/v3/organizations.rs index 3ff46b06e3..527592bced 100644 --- a/apps/labrinth/src/routes/v3/organizations.rs +++ b/apps/labrinth/src/routes/v3/organizations.rs @@ -4,7 +4,8 @@ use std::collections::HashMap; use super::ApiError; use crate::auth::checks::is_visible_organization; use crate::auth::{ - filter_visible_projects, get_user_from_headers, require_verified_email, + StandingRequirement, filter_visible_projects, get_user_from_headers, + require_verified_email, }; use crate::database::PgPool; use crate::database::models::team_item::DBTeamMember; @@ -62,6 +63,7 @@ pub async fn organization_projects_get( &redis, &session_queue, Scopes::ORGANIZATION_READ | Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -137,6 +139,7 @@ pub async fn organization_create( &redis, &session_queue, Scopes::ORGANIZATION_CREATE, + StandingRequirement::Full, ) .await? .1; @@ -244,6 +247,7 @@ pub async fn organization_get( &redis, &session_queue, Scopes::ORGANIZATION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -331,6 +335,7 @@ pub async fn organization_notes_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -455,6 +460,7 @@ pub async fn organizations_get( &redis, &session_queue, Scopes::ORGANIZATION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -560,6 +566,7 @@ pub async fn organizations_edit( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -742,6 +749,7 @@ pub async fn organization_delete( &redis, &session_queue, Scopes::ORGANIZATION_DELETE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -931,6 +939,7 @@ pub async fn organization_projects_add( &redis, &session_queue, Scopes::PROJECT_WRITE | Scopes::ORGANIZATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1113,6 +1122,7 @@ pub async fn organization_projects_remove( &redis, &session_queue, Scopes::PROJECT_WRITE | Scopes::ORGANIZATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1326,6 +1336,7 @@ pub async fn organization_icon_edit( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1443,6 +1454,7 @@ pub async fn delete_organization_icon( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/payouts.rs b/apps/labrinth/src/routes/v3/payouts.rs index c199d1bff0..52ee6eef0e 100644 --- a/apps/labrinth/src/routes/v3/payouts.rs +++ b/apps/labrinth/src/routes/v3/payouts.rs @@ -1,5 +1,7 @@ use crate::auth::validate::get_user_record_from_bearer_token; -use crate::auth::{AuthenticationError, get_user_from_headers}; +use crate::auth::{ + AuthenticationError, StandingRequirement, get_user_from_headers, +}; use crate::database::PgPool; use crate::database::models::DBUserId; use crate::database::models::{generate_payout_id, users_compliance}; @@ -70,6 +72,7 @@ pub async fn post_compliance_form( &redis, &session_queue, Scopes::PAYOUTS_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -521,6 +524,7 @@ pub async fn calculate_fees( &redis, &session_queue, false, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -560,6 +564,7 @@ pub async fn create_payout( &redis, &session_queue, false, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -787,6 +792,7 @@ pub async fn transaction_history( &redis, &session_queue, Scopes::PAYOUTS_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -878,6 +884,7 @@ pub async fn cancel_payout( &redis, &session_queue, Scopes::PAYOUTS_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1061,6 +1068,7 @@ pub async fn get_balance( &redis, &session_queue, Scopes::PAYOUTS_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/project_creation.rs b/apps/labrinth/src/routes/v3/project_creation.rs index ecb9967b36..5cd9507780 100644 --- a/apps/labrinth/src/routes/v3/project_creation.rs +++ b/apps/labrinth/src/routes/v3/project_creation.rs @@ -1,6 +1,7 @@ use super::version_creation::{InitialVersionData, try_create_version_fields}; use crate::auth::{ - AuthenticationError, get_user_from_headers, require_verified_email, + AuthenticationError, StandingRequirement, get_user_from_headers, + require_verified_email, }; use crate::database::PgPool; use crate::database::PgTransaction; @@ -482,6 +483,7 @@ async fn project_create_inner( redis, session_queue, Scopes::PROJECT_CREATE, + StandingRequirement::Full, ) .await?; diff --git a/apps/labrinth/src/routes/v3/project_creation/new.rs b/apps/labrinth/src/routes/v3/project_creation/new.rs index 2922375796..1d4b877f7f 100644 --- a/apps/labrinth/src/routes/v3/project_creation/new.rs +++ b/apps/labrinth/src/routes/v3/project_creation/new.rs @@ -1,3 +1,4 @@ +use crate::auth::StandingRequirement; use actix_http::StatusCode; use actix_web::{HttpRequest, HttpResponse, ResponseError, put, web}; use eyre::eyre; @@ -136,6 +137,7 @@ pub async fn create( &redis, &session_queue, Scopes::PROJECT_CREATE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating project creator")?; diff --git a/apps/labrinth/src/routes/v3/projects.rs b/apps/labrinth/src/routes/v3/projects.rs index a919536bee..cbbdc51f98 100644 --- a/apps/labrinth/src/routes/v3/projects.rs +++ b/apps/labrinth/src/routes/v3/projects.rs @@ -4,7 +4,9 @@ use std::cmp::Reverse; use std::collections::HashMap; use crate::auth::checks::{filter_visible_versions, is_visible_project}; -use crate::auth::{filter_visible_projects, get_user_from_headers}; +use crate::auth::{ + StandingRequirement, filter_visible_projects, get_user_from_headers, +}; use crate::database::models::notification_item::NotificationBuilder; use crate::database::models::project_item::{DBGalleryItem, DBModCategory}; use crate::database::models::thread_item::ThreadMessageBuilder; @@ -260,6 +262,7 @@ pub async fn projects_get( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -307,6 +310,7 @@ pub async fn project_get_internal( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|(_, user)| user) @@ -452,6 +456,7 @@ pub async fn project_edit_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1581,6 +1586,7 @@ pub async fn dependency_list_internal( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -1739,6 +1745,7 @@ pub async fn projects_edit( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2116,6 +2123,7 @@ pub async fn project_icon_edit_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2269,6 +2277,7 @@ pub async fn delete_project_icon_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2432,6 +2441,7 @@ pub async fn add_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2638,6 +2648,7 @@ pub async fn edit_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -2859,6 +2870,7 @@ pub async fn delete_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3000,6 +3012,7 @@ pub async fn project_delete_internal( &redis, &session_queue, Scopes::PROJECT_DELETE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -3310,6 +3323,7 @@ pub async fn project_follow_internal( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3415,6 +3429,7 @@ pub async fn project_unfollow_internal( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -3502,6 +3517,7 @@ pub async fn project_get_organization( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::ORGANIZATION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/v3/reports.rs b/apps/labrinth/src/routes/v3/reports.rs index d3ad4136e0..021146225b 100644 --- a/apps/labrinth/src/routes/v3/reports.rs +++ b/apps/labrinth/src/routes/v3/reports.rs @@ -1,4 +1,6 @@ -use crate::auth::{check_is_moderator_from_headers, get_user_from_headers}; +use crate::auth::{ + StandingRequirement, check_is_moderator_from_headers, get_user_from_headers, +}; use crate::database; use crate::database::PgPool; use crate::database::models::SharedInstanceId; @@ -85,6 +87,7 @@ pub async fn report_create( &redis, &session_queue, Scopes::REPORT_CREATE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -410,6 +413,7 @@ pub async fn reports( &redis, &session_queue, Scopes::REPORT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -520,6 +524,7 @@ pub async fn reports_get( &redis, &session_queue, Scopes::REPORT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -559,6 +564,7 @@ pub async fn report_get( &redis, &session_queue, Scopes::REPORT_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -616,6 +622,7 @@ pub async fn report_edit( &redis, &session_queue, Scopes::REPORT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -753,6 +760,7 @@ pub async fn report_delete( &redis, &session_queue, Scopes::REPORT_DELETE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/teams.rs b/apps/labrinth/src/routes/v3/teams.rs index 5cb8e173bb..9ae716d842 100644 --- a/apps/labrinth/src/routes/v3/teams.rs +++ b/apps/labrinth/src/routes/v3/teams.rs @@ -1,5 +1,5 @@ use crate::auth::checks::{is_visible_organization, is_visible_project}; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::DBProject; use crate::database::PgPool; use crate::database::models::notification_item::NotificationBuilder; @@ -72,6 +72,7 @@ pub async fn team_members_get_project_internal( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -161,6 +162,7 @@ pub async fn team_members_get_organization( &redis, &session_queue, Scopes::ORGANIZATION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -261,6 +263,7 @@ pub async fn team_members_get( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -352,6 +355,7 @@ pub async fn teams_get( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -418,6 +422,7 @@ pub async fn join_team( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -536,6 +541,7 @@ pub async fn add_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -822,6 +828,7 @@ pub async fn edit_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1035,6 +1042,7 @@ pub async fn transfer_ownership( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1250,6 +1258,7 @@ pub async fn remove_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/threads.rs b/apps/labrinth/src/routes/v3/threads.rs index 4d7d82167d..c4e1037d1a 100644 --- a/apps/labrinth/src/routes/v3/threads.rs +++ b/apps/labrinth/src/routes/v3/threads.rs @@ -1,4 +1,4 @@ -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::database::models::image_item; @@ -297,6 +297,7 @@ pub async fn thread_get( &redis, &session_queue, Scopes::THREAD_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -370,6 +371,7 @@ pub async fn threads_get( &redis, &session_queue, Scopes::THREAD_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -427,6 +429,7 @@ pub async fn thread_send_message( &redis, &session_queue, Scopes::THREAD_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -697,6 +700,7 @@ pub async fn message_delete( &redis, &session_queue, Scopes::THREAD_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/users.rs b/apps/labrinth/src/routes/v3/users.rs index 3ec7205286..2018c7f2e7 100644 --- a/apps/labrinth/src/routes/v3/users.rs +++ b/apps/labrinth/src/routes/v3/users.rs @@ -1,3 +1,5 @@ +use crate::auth::StandingRequirement; +use crate::auth::validate::get_full_user_from_headers; use crate::util::error::ApiContext as _; use std::{ cmp::Reverse, @@ -14,7 +16,9 @@ use crate::{ filter_visible_collections, filter_visible_projects, get_user_from_headers, }, - database::models::{DBModerationNote, DBOrganization, DBProjectId, DBUser}, + database::models::{ + DBModerationNote, DBOrganization, DBProjectId, DBUser, DBUserId, + }, file_hosting::{FileHost, FileHostPublicity}, models::{ ids::OrganizationId, @@ -22,7 +26,7 @@ use crate::{ organizations::Organization, pats::Scopes, projects::Project, - users::{Badges, Role}, + users::{AccountStanding, Badges, Role, User}, }, queue::session::AuthQueue, util::{img::delete_old_images, routes::read_limited_from_payload}, @@ -84,6 +88,7 @@ pub async fn all_projects( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -232,6 +237,7 @@ pub async fn admin_user_email( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -298,6 +304,7 @@ pub async fn projects_list( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -345,16 +352,26 @@ pub async fn user_auth_get( redis: web::Data, session_queue: web::Data, ) -> Result { - let (scopes, mut user) = get_user_from_headers( + let (scopes, db_user) = get_full_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::USER_READ, + StandingRequirement::None, ) .await .wrap_auth_err("authenticating API request")?; + let mut user = match db_user.account_standing { + AccountStanding::Full => User::from_full(db_user), + AccountStanding::Locked => { + let mut user = User::from(db_user); + user.account_standing = Some(AccountStanding::Locked); + return Ok(HttpResponse::Ok().json(user)); + } + }; + if !scopes.contains(Scopes::USER_READ_EMAIL) { user.email = None; } @@ -388,6 +405,7 @@ pub async fn get_user_preferences( &redis, &session_queue, Scopes::USER_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -432,6 +450,7 @@ pub async fn edit_user_preferences( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -536,18 +555,27 @@ pub async fn users_get( .await .wrap_internal_err("fetching users from database")?; - let auth_user = get_user_from_headers( + let auth_user = get_full_user_from_headers( &req, &**pool, &redis, &session_queue, - Scopes::SESSION_ACCESS, + Scopes::empty(), + StandingRequirement::None, ) .await - .map(|x| x.1) .ok(); - let notes = if auth_user.as_ref().is_some_and(|x| x.role.is_mod()) { + let is_mod = auth_user.as_ref().is_some_and(|(scopes, user)| { + match user.account_standing { + AccountStanding::Full => { + scopes.contains(Scopes::SESSION_ACCESS) + && Role::from_string(&user.role).is_mod() + } + AccountStanding::Locked => false, + } + }); + let notes = if is_mod { DBModerationNote::get_many_users( &users_data.iter().map(|x| x.id).collect::>(), &**pool, @@ -559,13 +587,22 @@ pub async fn users_get( HashMap::new() }; - let users: Vec = users_data + let users: Vec = users_data .into_iter() .map(|data| { - let mut user = crate::models::users::User::from(data.clone()); - if auth_user.as_ref().is_some_and(|x| x.role.is_mod()) { + let user_id = data.id; + let visible_standing = auth_user + .as_ref() + .filter(|(_, viewer)| { + viewer.id == user_id + || Role::from_string(&viewer.role).is_admin() + }) + .map(|_| data.account_standing); + let mut user = User::from(data); + user.account_standing = visible_standing; + if is_mod { user.moderation_notes = - Some(notes.get(&data.id).cloned().map(Into::into)); + Some(notes.get(&user_id).cloned().map(Into::into)); } user }) @@ -598,27 +635,43 @@ pub async fn user_get( .wrap_internal_err("fetching user from database")?; if let Some(data) = user_data { - let auth_user = get_user_from_headers( + let auth_user = get_full_user_from_headers( &req, &**pool, &redis, &session_queue, - Scopes::SESSION_ACCESS, + Scopes::empty(), + StandingRequirement::None, ) .await - .map(|x| x.1) .ok(); - let is_admin = auth_user.as_ref().is_some_and(|x| x.role.is_admin()); - let is_mod = auth_user.as_ref().is_some_and(|x| x.role.is_mod()); + let staff_role = + auth_user.as_ref().and_then(|(scopes, user)| { + match user.account_standing { + AccountStanding::Full => scopes + .contains(Scopes::SESSION_ACCESS) + .then(|| Role::from_string(&user.role)), + AccountStanding::Locked => None, + } + }); + let is_admin = staff_role.as_ref().is_some_and(Role::is_admin); + let is_mod = staff_role.as_ref().is_some_and(Role::is_mod); let user_id = data.id; - - let mut response: crate::models::users::User = if is_admin { + let visible_standing = auth_user + .as_ref() + .filter(|(_, viewer)| { + viewer.id == user_id + || Role::from_string(&viewer.role).is_admin() + }) + .map(|_| data.account_standing); + + let mut response = if is_admin { let github_id = data.github_id.and_then(|id| u64::try_from(id).ok()); let discord_id = data.discord_id.map(|id| id.to_string()); let steam_id = data.steam_id.map(|id| id.to_string()); - let mut user = crate::models::users::User::from_full(data); + let mut user = User::from_full(data); user.github_id = github_id; user.discord_id = discord_id; user.steam_id = steam_id; @@ -627,6 +680,8 @@ pub async fn user_get( data.into() }; + response.account_standing = visible_standing; + if is_mod { let note = DBModerationNote::get_user(user_id, &**pool, &redis) .await @@ -656,6 +711,7 @@ pub async fn user_notes_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -741,6 +797,7 @@ pub async fn collections_list( &redis, &session_queue, Scopes::COLLECTION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -788,6 +845,7 @@ pub async fn orgs_list( &redis, &session_queue, Scopes::PROJECT_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -891,6 +949,7 @@ pub struct EditUser { #[validate(length(max = 160))] pub bio: Option>, pub role: Option, + pub account_standing: Option, pub badges: Option, #[validate(length(max = 160))] pub venmo_handle: Option, @@ -924,6 +983,7 @@ pub async fn user_edit( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")?; @@ -989,6 +1049,25 @@ pub async fn user_edit( .execute(&mut transaction) .await .wrap_internal_err("fetching bio from database")?; + } + + if let Some(account_standing) = new_user.account_standing { + if !user.role.is_admin() { + return Err(ApiError::Auth(eyre::eyre!( + "only admins can edit account standing" + ))); + } + + sqlx::query!( + r#" + UPDATE users SET account_standing = $1 WHERE id = $2 + "#, + account_standing.as_str(), + id as DBUserId, + ) + .execute(&mut transaction) + .await + .wrap_internal_err("updating account standing")?; } if let Some(role) = &new_user.role { @@ -1143,6 +1222,7 @@ pub async fn user_icon_edit( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1238,6 +1318,7 @@ pub async fn user_icon_delete( &redis, &session_queue, Scopes::USER_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1309,6 +1390,7 @@ pub async fn user_delete( &redis, &session_queue, Scopes::USER_DELETE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1372,6 +1454,7 @@ pub async fn user_follows( &redis, &session_queue, Scopes::USER_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1432,6 +1515,7 @@ pub async fn user_notifications( &redis, &session_queue, Scopes::NOTIFICATION_READ, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/version_creation.rs b/apps/labrinth/src/routes/v3/version_creation.rs index ea375ff76c..dcfd7fe253 100644 --- a/apps/labrinth/src/routes/v3/version_creation.rs +++ b/apps/labrinth/src/routes/v3/version_creation.rs @@ -1,5 +1,5 @@ use super::project_creation::{CreateError, UploadedFile}; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::PgTransaction; use crate::database::models::loader_fields::{ @@ -223,6 +223,7 @@ async fn version_create_inner( redis, session_queue, Scopes::VERSION_CREATE, + StandingRequirement::Full, ) .await? .1; @@ -705,6 +706,7 @@ async fn upload_file_to_version_inner( &redis, session_queue, Scopes::VERSION_WRITE, + StandingRequirement::Full, ) .await? .1; diff --git a/apps/labrinth/src/routes/v3/version_file.rs b/apps/labrinth/src/routes/v3/version_file.rs index 79eeb462eb..f3cda142d3 100644 --- a/apps/labrinth/src/routes/v3/version_file.rs +++ b/apps/labrinth/src/routes/v3/version_file.rs @@ -1,6 +1,8 @@ use super::ApiError; use crate::auth::checks::{filter_visible_versions, is_visible_version}; -use crate::auth::{filter_visible_projects, get_user_from_headers}; +use crate::auth::{ + StandingRequirement, filter_visible_projects, get_user_from_headers, +}; use crate::database::PgPool; use crate::database::ReadOnlyPgPool; use crate::models::ids::VersionId; @@ -78,6 +80,7 @@ pub async fn get_version_from_hash( &redis, &session_queue, Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -210,6 +213,7 @@ pub async fn get_update_from_hash( &redis, &session_queue, Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -333,6 +337,7 @@ pub async fn get_versions_from_hashes( &redis, &session_queue, Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -416,6 +421,7 @@ pub async fn get_projects_from_hashes( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -697,6 +703,7 @@ pub async fn update_individual_files( &redis, &session_queue, Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -857,6 +864,7 @@ pub async fn delete_file( &redis, &session_queue, Scopes::VERSION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1041,6 +1049,7 @@ pub async fn download_version( &redis, &session_queue, Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/v3/versions.rs b/apps/labrinth/src/routes/v3/versions.rs index 99eb84f19b..ff771af725 100644 --- a/apps/labrinth/src/routes/v3/versions.rs +++ b/apps/labrinth/src/routes/v3/versions.rs @@ -5,7 +5,7 @@ use super::ApiError; use crate::auth::checks::{ filter_visible_versions, is_visible_project, is_visible_version, }; -use crate::auth::get_user_from_headers; +use crate::auth::{StandingRequirement, get_user_from_headers}; use crate::database; use crate::database::models::loader_fields::{ self, LoaderField, LoaderFieldEnumValue, VersionField, @@ -90,6 +90,7 @@ pub async fn version_project_get_helper( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -217,6 +218,7 @@ pub async fn versions_get( &redis, &session_queue, Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -299,6 +301,7 @@ pub async fn version_get_helper( &redis, &session_queue, Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -470,6 +473,7 @@ pub async fn version_edit_helper( &redis, &session_queue, Scopes::VERSION_WRITE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? @@ -1033,6 +1037,7 @@ pub async fn version_list_internal( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, + StandingRequirement::Full, ) .await .map(|x| x.1) @@ -1231,6 +1236,7 @@ pub async fn version_delete( &redis, &session_queue, Scopes::VERSION_DELETE, + StandingRequirement::Full, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/tests/account_standing.rs b/apps/labrinth/tests/account_standing.rs new file mode 100644 index 0000000000..694e67a1af --- /dev/null +++ b/apps/labrinth/tests/account_standing.rs @@ -0,0 +1,358 @@ +#![recursion_limit = "256"] + +use actix_http::StatusCode; +use actix_web::{http::Method, test}; +use chrono::{Duration, Utc}; +use common::{ + api_common::AppendsOptionalPat, + api_v3::{ + ApiV3, + oauth::{ + get_auth_code_from_redirect_params, get_authorize_accept_flow_id, + }, + }, + database::{ADMIN_USER_PAT, FRIEND_USER_PAT, MOD_USER_PAT, USER_USER_PAT}, + environment::{TestEnvironment, with_test_environment}, +}; +use labrinth::{ + auth::{AuthenticationError, StandingRequirement, get_user_from_headers}, + database::models::{ + DBUserId, flow_item::DBFlow, session_item::SessionBuilder, + }, + env::ENV, + models::{pats::Scopes, users::AccountStanding}, + queue::session::AuthQueue, +}; +use serde_json::{Value, json}; + +pub mod common; + +async fn set_standing( + env: &TestEnvironment, + user: &str, + standing: AccountStanding, +) { + let response = env + .call( + test::TestRequest::patch() + .uri(&format!("/v3/user/{user}")) + .append_pat(ADMIN_USER_PAT) + .set_json(json!({"account_standing": standing})) + .to_request(), + ) + .await; + assert_status!(&response, StatusCode::NO_CONTENT); +} + +#[actix_rt::test] +async fn standing_is_visible_only_to_self_and_admin_in_both_api_versions() { + with_test_environment(None, |env: TestEnvironment| async move { + let mut restricted_tokens = Vec::new(); + for pat in [USER_USER_PAT, ADMIN_USER_PAT] { + let response = env + .call( + test::TestRequest::post() + .uri("/_internal/pat") + .append_pat(pat) + .set_json(json!({ + "name": "standing visibility", + "scopes": Scopes::empty(), + "expires": Utc::now() + Duration::days(1), + })) + .to_request(), + ) + .await; + assert_status!(&response, StatusCode::OK); + let body: Value = test::read_body_json(response).await; + restricted_tokens + .push(body["access_token"].as_str().unwrap().to_owned()); + } + for standing in [AccountStanding::Full, AccountStanding::Locked] { + set_standing(&env, "3", standing).await; + for version in ["v2", "v3"] { + for (pat, visible) in [ + (None, false), + (FRIEND_USER_PAT, false), + (MOD_USER_PAT, false), + (USER_USER_PAT, true), + (ADMIN_USER_PAT, true), + (Some(restricted_tokens[0].as_str()), true), + (Some(restricted_tokens[1].as_str()), true), + ] { + for target in ["3", "User"] { + let response = env + .call( + test::TestRequest::get() + .uri(&format!("/{version}/user/{target}")) + .append_pat(pat) + .to_request(), + ) + .await; + assert_status!(&response, StatusCode::OK); + let body: Value = test::read_body_json(response).await; + assert_eq!( + body.get("account_standing"), + visible.then(|| json!(standing)).as_ref() + ); + assert_eq!( + body["email"].is_string(), + pat == ADMIN_USER_PAT + ); + if restricted_tokens + .iter() + .any(|token| pat == Some(token.as_str())) + { + for field in [ + "github_id", + "discord_id", + "steam_id", + "moderation_notes", + "payout_data", + ] { + assert!( + body[field].is_null(), + "{field} requires session access" + ); + } + } + } + + let response = env + .call( + test::TestRequest::get() + .uri(&format!( + "/{version}/users?ids=%5B%223%22,%225%22%5D" + )) + .append_pat(pat) + .to_request(), + ) + .await; + assert_status!(&response, StatusCode::OK); + let body: Vec = test::read_body_json(response).await; + assert_eq!(body.len(), 2); + for user in body { + let expected = if user["id"] == "3" { + visible.then(|| json!(standing)) + } else { + (pat == ADMIN_USER_PAT + || pat == Some(restricted_tokens[1].as_str())) + .then(|| json!("full")) + }; + assert_eq!( + user.get("account_standing"), + expected.as_ref() + ); + } + } + let response = env + .call( + test::TestRequest::get() + .uri(&format!("/{version}/user")) + .append_pat(USER_USER_PAT) + .to_request(), + ) + .await; + assert_status!(&response, StatusCode::OK); + let body: Value = test::read_body_json(response).await; + assert_eq!(body["account_standing"], json!(standing)); + match standing { + AccountStanding::Full => assert!(body["email"].is_string()), + AccountStanding::Locked => { + for field in [ + "email", + "email_verified", + "auth_providers", + "has_password", + "has_totp", + "payout_data", + "stripe_customer_id", + "allow_friend_requests", + "eligibility_verified_at", + ] { + assert!( + body[field].is_null(), + "{field} must not be exposed to a locked account" + ); + } + } + } + } + } + }) + .await; +} + +#[actix_rt::test] +async fn existing_pat_session_and_oauth_credentials_obey_standing_and_cache_invalidation() + { + with_test_environment(None, |env: TestEnvironment| async move { + let client = &env.dummy.oauth_client_alpha; + let oauth_token = env + .api + .complete_full_authorize_flow( + &client.client_id, + &client.client_secret, + Some("USER_READ USER_WRITE"), + None, + None, + USER_USER_PAT, + ) + .await; + let mut transaction = env.db.pool.begin().await.unwrap(); + SessionBuilder { + session: "mra_standing_test".into(), + user_id: DBUserId(3), + os: None, + platform: None, + city: None, + country: None, + ip: "127.0.0.1".into(), + user_agent: "standing test".into(), + expires: None, + session_expires: None, + } + .insert(&mut transaction) + .await + .unwrap(); + transaction.commit().await.unwrap(); + let tokens = [ + USER_USER_PAT.unwrap(), + "mra_standing_test", + oauth_token.as_str(), + ]; + let queue = AuthQueue::new(); + for standing in [ + AccountStanding::Full, + AccountStanding::Locked, + AccountStanding::Full, + ] { + set_standing(&env, "3", standing).await; + for token in tokens { + let req = test::TestRequest::get() + .append_pat(Some(token)) + .append_header(( + "x-ratelimit-key", + ENV.RATE_LIMIT_IGNORE_KEY.as_str(), + )) + .to_http_request(); + for requirement in + [StandingRequirement::None, StandingRequirement::Full] + { + let result = get_user_from_headers( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::USER_READ, + requirement, + ) + .await; + match (standing, requirement) { + ( + AccountStanding::Locked, + StandingRequirement::Full, + ) => { + assert!(matches!( + result, + Err(AuthenticationError::AccountLocked) + )); + } + (AccountStanding::Full, StandingRequirement::Full) + | ( + AccountStanding::Full | AccountStanding::Locked, + StandingRequirement::None, + ) => { + assert_eq!( + result.unwrap().1.account_standing, + Some(standing) + ); + } + } + } + } + } + let req = test::TestRequest::get() + .append_pat(Some(&oauth_token)) + .to_http_request(); + let result = get_user_from_headers( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::USER_AUTH_WRITE, + StandingRequirement::None, + ) + .await; + assert!(matches!( + result, + Err(AuthenticationError::InvalidCredentials) + )); + }) + .await; +} + +#[actix_rt::test] +async fn locked_accounts_cannot_mutate_resources_read_sensitive_data_or_unlock_themselves() + { + with_test_environment(None, |env: TestEnvironment| async move { + for pat in [USER_USER_PAT, MOD_USER_PAT] { + let response = env.call(test::TestRequest::patch().uri("/v3/user/3") + .append_pat(pat).set_json(json!({"account_standing": "locked"})).to_request()).await; + assert_status!(&response, StatusCode::UNAUTHORIZED); + } + set_standing(&env, "3", AccountStanding::Locked).await; + let project = format!("/v3/project/{}", env.dummy.project_alpha.project_id); + for (method, uri, body) in [ + (Method::PATCH, "/v3/user/3", json!({"account_standing":"full"})), + (Method::PATCH, project.as_str(), json!({"title":"changed"})), + (Method::PATCH, "/v3/organization/missing", json!({"name":"changed"})), + (Method::PATCH, "/v3/collection/missing", json!({"name":"changed"})), + (Method::GET, "/_internal/pat", json!(null)), + (Method::POST, "/_internal/pat", json!({"name":"locked token", "scopes":0, "expires":"2099-01-01T00:00:00Z"})), + (Method::GET, "/_internal/billing/subscriptions", json!(null)), + (Method::GET, "/_internal/billing/payment_methods", json!(null)), + ] { + let response = env.call(test::TestRequest::default().method(method).uri(uri) + .append_pat(USER_USER_PAT).set_json(body).to_request()).await; + assert_status!(&response, StatusCode::FORBIDDEN); + let body: Value = test::read_body_json(response).await; + assert_eq!(body["error"], "auth_error", "{uri}"); + } + set_standing(&env, "1", AccountStanding::Locked).await; + let response = env.call(test::TestRequest::patch().uri("/v3/user/3") + .append_pat(ADMIN_USER_PAT).set_json(json!({"account_standing":"full"})).to_request()).await; + assert_status!(&response, StatusCode::FORBIDDEN); + }).await; +} + +#[actix_rt::test] +async fn flows_created_before_lock_cannot_reset_password_verify_email_or_issue_tokens() + { + with_test_environment(None, |env: TestEnvironment| async move { + let password_flow = DBFlow::ForgotPassword { user_id: DBUserId(3) }.insert(Duration::hours(1), &env.db.redis_pool).await.unwrap(); + let email_flow = DBFlow::ConfirmEmail { user_id: DBUserId(3), confirm_email: "user@modrinth.com".into() }.insert(Duration::hours(1), &env.db.redis_pool).await.unwrap(); + let login_flow = DBFlow::Login2FA { user_id: DBUserId(3) }.insert(Duration::hours(1), &env.db.redis_pool).await.unwrap(); + let client = &env.dummy.oauth_client_alpha; + let response = env.api.oauth_authorize(&client.client_id, Some("USER_READ"), None, None, USER_USER_PAT).await; + let flow = get_authorize_accept_flow_id(response).await; + let response = env.api.oauth_accept(&flow, USER_USER_PAT).await; + let auth_code = get_auth_code_from_redirect_params(&response).await; + set_standing(&env, "3", AccountStanding::Locked).await; + + for (method, uri, body) in [ + (Method::PATCH, "/_internal/auth/password", json!({"flow":password_flow,"new_password":"a long secure test password 489313"})), + (Method::POST, "/_internal/auth/email/verify", json!({"flow":email_flow})), + (Method::POST, "/_internal/auth/login/2fa", json!({"flow":login_flow,"code":"123456"})), + ] { + let response = env.call(test::TestRequest::default().method(method).uri(uri).set_json(body).to_request()).await; + assert_status!(&response, StatusCode::FORBIDDEN); + let body: Value = test::read_body_json(response).await; + assert_eq!(body["error"], "auth_error"); + } + let response = env.api.oauth_token(auth_code, None, client.client_id.clone(), &client.client_secret).await; + assert_status!(&response, StatusCode::BAD_REQUEST); + let body: Value = test::read_body_json(response).await; + assert_eq!(body["error"], "invalid_grant"); + assert!(body.get("access_token").is_none()); + }).await; +} From 16462cf008e93b4f130d4c9bb0acec0c0a03cf56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-X=2E=20T=2E?= Date: Fri, 4 Sep 2026 23:23:15 -0400 Subject: [PATCH 2/4] change to "account_locked" --- ...d6352f8502009aa619b1f46f2592ad5de9f3.json} | 8 +- ...d7f634b673b5a65745d8d4f172b7de2478745.json | 15 --- ...5b0b58a90a2e5252d4f54943fe640863c47a3.json | 15 +++ ...0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json | 35 ------- ...54aed5d31564ec8896635f81ded9b02153d7.json} | 4 +- ...a92d5f36efaf4aac771cbd2575b10613dd87.json} | 4 +- ...5f51617eaa22cb287b266c6bca7b54bf8cfce.json | 35 +++++++ ...00bcbf16cd76005e9d029d2153a5504016cc.json} | 6 +- ...892405dd4df00fa9548c643ea8e33e4f06df.json} | 8 +- .../20260904120000_account_locked.sql | 2 + .../20260904120000_account_standing.sql | 2 - apps/labrinth/src/auth/mod.rs | 2 +- apps/labrinth/src/auth/oauth/mod.rs | 6 +- apps/labrinth/src/auth/validate.rs | 47 +++++----- .../src/database/models/oauth_token_item.rs | 2 +- .../src/database/models/passkey_item.rs | 7 +- .../labrinth/src/database/models/user_item.rs | 12 +-- .../src/database/models/users_redeemals.rs | 7 +- apps/labrinth/src/models/v2/user.rs | 6 +- apps/labrinth/src/models/v3/users.rs | 35 +------ apps/labrinth/src/routes/analytics.rs | 8 +- apps/labrinth/src/routes/internal/admin.rs | 4 +- .../labrinth/src/routes/internal/affiliate.rs | 14 +-- .../src/routes/internal/analytics_event.rs | 8 +- .../src/routes/internal/attribution.rs | 19 ++-- apps/labrinth/src/routes/internal/billing.rs | 26 ++--- .../src/routes/internal/delphi/mod.rs | 8 +- .../routes/internal/external_notifications.rs | 4 +- apps/labrinth/src/routes/internal/flows.rs | 88 ++++++++--------- apps/labrinth/src/routes/internal/gdpr.rs | 4 +- apps/labrinth/src/routes/internal/medal.rs | 14 +-- .../internal/moderation/external_license.rs | 14 +-- .../src/routes/internal/moderation/mod.rs | 30 +++--- .../routes/internal/moderation/tech_review.rs | 26 ++--- .../internal/moderation/tech_review/global.rs | 6 +- .../internal/moderation/tech_review/rules.rs | 14 +-- .../moderation/tech_review/rules_scan.rs | 8 +- apps/labrinth/src/routes/internal/pats.rs | 10 +- apps/labrinth/src/routes/internal/privacy.rs | 8 +- .../src/routes/internal/server_ping.rs | 4 +- apps/labrinth/src/routes/internal/session.rs | 8 +- apps/labrinth/src/routes/internal/statuses.rs | 4 +- apps/labrinth/src/routes/maven.rs | 10 +- apps/labrinth/src/routes/mod.rs | 2 +- apps/labrinth/src/routes/updates.rs | 4 +- apps/labrinth/src/routes/v2/users.rs | 6 +- .../src/routes/v3/analytics_get/facets/mod.rs | 4 +- .../src/routes/v3/analytics_get/mod.rs | 4 +- apps/labrinth/src/routes/v3/blocked_users.rs | 8 +- apps/labrinth/src/routes/v3/collections.rs | 16 ++-- apps/labrinth/src/routes/v3/content/mod.rs | 4 +- apps/labrinth/src/routes/v3/disclosures.rs | 6 +- apps/labrinth/src/routes/v3/friends.rs | 8 +- apps/labrinth/src/routes/v3/images.rs | 4 +- apps/labrinth/src/routes/v3/limits.rs | 8 +- apps/labrinth/src/routes/v3/notifications.rs | 14 +-- apps/labrinth/src/routes/v3/oauth_clients.rs | 18 ++-- apps/labrinth/src/routes/v3/organizations.rs | 24 ++--- apps/labrinth/src/routes/v3/payouts.rs | 14 +-- .../src/routes/v3/project_creation.rs | 4 +- .../src/routes/v3/project_creation/new.rs | 4 +- apps/labrinth/src/routes/v3/projects/mod.rs | 30 +++--- .../src/routes/v3/projects/validate.rs | 3 +- apps/labrinth/src/routes/v3/reports.rs | 15 +-- apps/labrinth/src/routes/v3/teams.rs | 20 ++-- apps/labrinth/src/routes/v3/threads.rs | 10 +- apps/labrinth/src/routes/v3/users.rs | 82 ++++++++-------- .../src/routes/v3/version_creation.rs | 6 +- apps/labrinth/src/routes/v3/version_file.rs | 16 ++-- apps/labrinth/src/routes/v3/versions.rs | 14 +-- ...{account_standing.rs => account_locked.rs} | 94 +++++++++---------- 71 files changed, 500 insertions(+), 539 deletions(-) rename apps/labrinth/.sqlx/{query-f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8.json => query-2021fcddfb0df923e4c7229e59ded6352f8502009aa619b1f46f2592ad5de9f3.json} (73%) delete mode 100644 apps/labrinth/.sqlx/query-211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745.json create mode 100644 apps/labrinth/.sqlx/query-8496416e98bbb63db17c89e0fe85b0b58a90a2e5252d4f54943fe640863c47a3.json delete mode 100644 apps/labrinth/.sqlx/query-9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json rename apps/labrinth/.sqlx/{query-d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df.json => query-a120bab0d2d0f721bade7fdb7a4854aed5d31564ec8896635f81ded9b02153d7.json} (77%) rename apps/labrinth/.sqlx/{query-71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e.json => query-a464c4a96fc8690891532af23faaa92d5f36efaf4aac771cbd2575b10613dd87.json} (70%) create mode 100644 apps/labrinth/.sqlx/query-bd98516db2f812b2655b57801875f51617eaa22cb287b266c6bca7b54bf8cfce.json rename apps/labrinth/.sqlx/{query-44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390.json => query-c33cd0ff271a396af728cae2df4900bcbf16cd76005e9d029d2153a5504016cc.json} (72%) rename apps/labrinth/.sqlx/{query-8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1.json => query-de703148611b2cdc3b5743046098892405dd4df00fa9548c643ea8e33e4f06df.json} (76%) create mode 100644 apps/labrinth/migrations/20260904120000_account_locked.sql delete mode 100644 apps/labrinth/migrations/20260904120000_account_standing.sql rename apps/labrinth/tests/{account_standing.rs => account_locked.rs} (81%) diff --git a/apps/labrinth/.sqlx/query-f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8.json b/apps/labrinth/.sqlx/query-2021fcddfb0df923e4c7229e59ded6352f8502009aa619b1f46f2592ad5de9f3.json similarity index 73% rename from apps/labrinth/.sqlx/query-f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8.json rename to apps/labrinth/.sqlx/query-2021fcddfb0df923e4c7229e59ded6352f8502009aa619b1f46f2592ad5de9f3.json index f74c8fb9ef..f6fbadb8e1 100644 --- a/apps/labrinth/.sqlx/query-f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8.json +++ b/apps/labrinth/.sqlx/query-2021fcddfb0df923e4c7229e59ded6352f8502009aa619b1f46f2592ad5de9f3.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT id, email,\n avatar_url, raw_avatar_url, username, bio,\n\t\t\t\t\t\tcreated, role, badges, account_standing AS \"account_standing: AccountStanding\",\n (\n SELECT MAX(campaign_donations.donated_at)\n FROM campaign_donations\n WHERE campaign_donations.user_id = users.id\n ) AS campaign_pride_26_last_donated_at,\n (\n SELECT SUM(campaign_donations.amount_usd)\n FROM campaign_donations\n WHERE campaign_donations.user_id = users.id\n ) AS campaign_pride_26_total_amount_donated_usd,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter,\n eligibility_verified_at\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n\t\t\t\t\t", + "query": "\n SELECT id, email,\n avatar_url, raw_avatar_url, username, bio,\n\t\t\t\t\t\tcreated, role, badges, account_locked,\n (\n SELECT MAX(campaign_donations.donated_at)\n FROM campaign_donations\n WHERE campaign_donations.user_id = users.id\n ) AS campaign_pride_26_last_donated_at,\n (\n SELECT SUM(campaign_donations.amount_usd)\n FROM campaign_donations\n WHERE campaign_donations.user_id = users.id\n ) AS campaign_pride_26_total_amount_donated_usd,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, totp_secret, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter,\n eligibility_verified_at\n FROM users\n WHERE id = ANY($1) OR LOWER(username) = ANY($2)\n\t\t\t\t\t", "describe": { "columns": [ { @@ -50,8 +50,8 @@ }, { "ordinal": 9, - "name": "account_standing: AccountStanding", - "type_info": "Varchar" + "name": "account_locked", + "type_info": "Bool" }, { "ordinal": 10, @@ -187,5 +187,5 @@ true ] }, - "hash": "f34fca2fe5161d28eac97054ebf4cb85f234d858851232d9688e62f9530651e8" + "hash": "2021fcddfb0df923e4c7229e59ded6352f8502009aa619b1f46f2592ad5de9f3" } diff --git a/apps/labrinth/.sqlx/query-211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745.json b/apps/labrinth/.sqlx/query-211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745.json deleted file mode 100644 index aac3563f17..0000000000 --- a/apps/labrinth/.sqlx/query-211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n\t\t\t\t\tUPDATE users SET account_standing = $1 WHERE id = $2\n\t\t\t\t\t", - "describe": { - "columns": [], - "parameters": { - "Left": [ - "Varchar", - "Int8" - ] - }, - "nullable": [] - }, - "hash": "211a340d0b067d3921f19a8ce5dd7f634b673b5a65745d8d4f172b7de2478745" -} diff --git a/apps/labrinth/.sqlx/query-8496416e98bbb63db17c89e0fe85b0b58a90a2e5252d4f54943fe640863c47a3.json b/apps/labrinth/.sqlx/query-8496416e98bbb63db17c89e0fe85b0b58a90a2e5252d4f54943fe640863c47a3.json new file mode 100644 index 0000000000..47715c2606 --- /dev/null +++ b/apps/labrinth/.sqlx/query-8496416e98bbb63db17c89e0fe85b0b58a90a2e5252d4f54943fe640863c47a3.json @@ -0,0 +1,15 @@ +{ + "db_name": "PostgreSQL", + "query": "\n\t\t\t\t\tUPDATE users SET account_locked = $1 WHERE id = $2\n\t\t\t\t\t", + "describe": { + "columns": [], + "parameters": { + "Left": [ + "Bool", + "Int8" + ] + }, + "nullable": [] + }, + "hash": "8496416e98bbb63db17c89e0fe85b0b58a90a2e5252d4f54943fe640863c47a3" +} diff --git a/apps/labrinth/.sqlx/query-9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json b/apps/labrinth/.sqlx/query-9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json deleted file mode 100644 index b2add6f5d5..0000000000 --- a/apps/labrinth/.sqlx/query-9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n SELECT\n users.id,\n\t\t\t\tusers.account_standing AS \"account_standing: AccountStanding\",\n users_redeemals.status AS \"status: Option\"\n FROM\n users\n LEFT JOIN\n users_redeemals ON users_redeemals.user_id = users.id\n AND users_redeemals.offer = $2\n WHERE\n users.username = $1\n ORDER BY\n users_redeemals.redeemed DESC\n LIMIT 1\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Int8" - }, - { - "ordinal": 1, - "name": "account_standing: AccountStanding", - "type_info": "Varchar" - }, - { - "ordinal": 2, - "name": "status: Option", - "type_info": "Varchar" - } - ], - "parameters": { - "Left": [ - "Text", - "Text" - ] - }, - "nullable": [ - false, - false, - false - ] - }, - "hash": "9502cc741c821532a6566bea44a0e2bbb34cfc47267e5bba1ec35ed3078baa7c" -} diff --git a/apps/labrinth/.sqlx/query-d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df.json b/apps/labrinth/.sqlx/query-a120bab0d2d0f721bade7fdb7a4854aed5d31564ec8896635f81ded9b02153d7.json similarity index 77% rename from apps/labrinth/.sqlx/query-d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df.json rename to apps/labrinth/.sqlx/query-a120bab0d2d0f721bade7fdb7a4854aed5d31564ec8896635f81ded9b02153d7.json index f86e75699a..b59305ead1 100644 --- a/apps/labrinth/.sqlx/query-d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df.json +++ b/apps/labrinth/.sqlx/query-a120bab0d2d0f721bade7fdb7a4854aed5d31564ec8896635f81ded9b02153d7.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO oauth_access_tokens (\n id, authorization_id, token_hash, scopes, last_used\n )\n\t\t\tSELECT $1, $2, $3, $4, $5\n\t\t\tFROM oauth_client_authorizations auths\n\t\t\tJOIN users ON users.id = auths.user_id\n\t\t\tWHERE auths.id = $2 AND users.account_standing = 'full'\n RETURNING created, expires\n ", + "query": "\n INSERT INTO oauth_access_tokens (\n id, authorization_id, token_hash, scopes, last_used\n )\n\t\t\tSELECT $1, $2, $3, $4, $5\n\t\t\tFROM oauth_client_authorizations auths\n\t\t\tJOIN users ON users.id = auths.user_id\n\t\t\tWHERE auths.id = $2 AND NOT users.account_locked\n RETURNING created, expires\n ", "describe": { "columns": [ { @@ -28,5 +28,5 @@ false ] }, - "hash": "d2bec14984e14f723514381d503271f2105b4596a9a391b9dd436ca9f86779df" + "hash": "a120bab0d2d0f721bade7fdb7a4854aed5d31564ec8896635f81ded9b02153d7" } diff --git a/apps/labrinth/.sqlx/query-71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e.json b/apps/labrinth/.sqlx/query-a464c4a96fc8690891532af23faaa92d5f36efaf4aac771cbd2575b10613dd87.json similarity index 70% rename from apps/labrinth/.sqlx/query-71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e.json rename to apps/labrinth/.sqlx/query-a464c4a96fc8690891532af23faaa92d5f36efaf4aac771cbd2575b10613dd87.json index 001af3460b..1fe976d774 100644 --- a/apps/labrinth/.sqlx/query-71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e.json +++ b/apps/labrinth/.sqlx/query-a464c4a96fc8690891532af23faaa92d5f36efaf4aac771cbd2575b10613dd87.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n UPDATE users\n SET paypal_country = $1, paypal_email = $2, paypal_id = $3\n\t\t\t\tWHERE id = $4 AND account_standing = 'full'\n ", + "query": "\n UPDATE users\n SET paypal_country = $1, paypal_email = $2, paypal_id = $3\n\t\t\t\tWHERE id = $4 AND NOT account_locked\n ", "describe": { "columns": [], "parameters": { @@ -13,5 +13,5 @@ }, "nullable": [] }, - "hash": "71d02443c1e35ab9ad119bed0efe46c7d1a6d64f04592b3610673ff3bd76707e" + "hash": "a464c4a96fc8690891532af23faaa92d5f36efaf4aac771cbd2575b10613dd87" } diff --git a/apps/labrinth/.sqlx/query-bd98516db2f812b2655b57801875f51617eaa22cb287b266c6bca7b54bf8cfce.json b/apps/labrinth/.sqlx/query-bd98516db2f812b2655b57801875f51617eaa22cb287b266c6bca7b54bf8cfce.json new file mode 100644 index 0000000000..e7b43d24cc --- /dev/null +++ b/apps/labrinth/.sqlx/query-bd98516db2f812b2655b57801875f51617eaa22cb287b266c6bca7b54bf8cfce.json @@ -0,0 +1,35 @@ +{ + "db_name": "PostgreSQL", + "query": "\n SELECT\n users.id,\n\t\t\t\tusers.account_locked,\n users_redeemals.status AS \"status: Option\"\n FROM\n users\n LEFT JOIN\n users_redeemals ON users_redeemals.user_id = users.id\n AND users_redeemals.offer = $2\n WHERE\n users.username = $1\n ORDER BY\n users_redeemals.redeemed DESC\n LIMIT 1\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "id", + "type_info": "Int8" + }, + { + "ordinal": 1, + "name": "account_locked", + "type_info": "Bool" + }, + { + "ordinal": 2, + "name": "status: Option", + "type_info": "Varchar" + } + ], + "parameters": { + "Left": [ + "Text", + "Text" + ] + }, + "nullable": [ + false, + false, + false + ] + }, + "hash": "bd98516db2f812b2655b57801875f51617eaa22cb287b266c6bca7b54bf8cfce" +} diff --git a/apps/labrinth/.sqlx/query-44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390.json b/apps/labrinth/.sqlx/query-c33cd0ff271a396af728cae2df4900bcbf16cd76005e9d029d2153a5504016cc.json similarity index 72% rename from apps/labrinth/.sqlx/query-44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390.json rename to apps/labrinth/.sqlx/query-c33cd0ff271a396af728cae2df4900bcbf16cd76005e9d029d2153a5504016cc.json index 7d23935e09..f40ebd372a 100644 --- a/apps/labrinth/.sqlx/query-44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390.json +++ b/apps/labrinth/.sqlx/query-c33cd0ff271a396af728cae2df4900bcbf16cd76005e9d029d2153a5504016cc.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n INSERT INTO users (\n id, username, email,\n avatar_url, raw_avatar_url, bio, created,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter,\n\t\t\t\teligibility_verified_at, account_standing\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9, $10, $11, $12, $13,\n $14, $15, $16, $17, $18, $19, $20, $21, $22,\n\t\t\t\t$23, $24\n )\n ", + "query": "\n INSERT INTO users (\n id, username, email,\n avatar_url, raw_avatar_url, bio, created,\n github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id,\n email_verified, password, paypal_id, paypal_country, paypal_email,\n venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter,\n\t\t\t\teligibility_verified_at, account_locked\n )\n VALUES (\n $1, $2, $3, $4, $5,\n $6, $7,\n $8, $9, $10, $11, $12, $13,\n $14, $15, $16, $17, $18, $19, $20, $21, $22,\n\t\t\t\t$23, $24\n )\n ", "describe": { "columns": [], "parameters": { @@ -28,10 +28,10 @@ "Bool", "Bool", "Timestamptz", - "Varchar" + "Bool" ] }, "nullable": [] }, - "hash": "44ed1e2bccc9bee729b30ce31c6422c85d06f1ad9f6dce7781511332e18f3390" + "hash": "c33cd0ff271a396af728cae2df4900bcbf16cd76005e9d029d2153a5504016cc" } diff --git a/apps/labrinth/.sqlx/query-8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1.json b/apps/labrinth/.sqlx/query-de703148611b2cdc3b5743046098892405dd4df00fa9548c643ea8e33e4f06df.json similarity index 76% rename from apps/labrinth/.sqlx/query-8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1.json rename to apps/labrinth/.sqlx/query-de703148611b2cdc3b5743046098892405dd4df00fa9548c643ea8e33e4f06df.json index e3f04ac4c0..3abc7c8136 100644 --- a/apps/labrinth/.sqlx/query-8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1.json +++ b/apps/labrinth/.sqlx/query-de703148611b2cdc3b5743046098892405dd4df00fa9548c643ea8e33e4f06df.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n\t\t\tSELECT user_passkeys.id, user_id, name, credential_id,\n passkey AS \"passkey: sqlx::types::Json\",\n\t\t\t\t last_used, created_at,\n\t\t\t\t users.account_standing AS \"account_standing: AccountStanding\"\n FROM user_passkeys\n\t\t\tJOIN users ON users.id = user_passkeys.user_id\n WHERE credential_id = $1\n ", + "query": "\n\t\t\tSELECT user_passkeys.id, user_id, name, credential_id,\n passkey AS \"passkey: sqlx::types::Json\",\n\t\t\t\t last_used, created_at,\n\t\t\t\t users.account_locked\n FROM user_passkeys\n\t\t\tJOIN users ON users.id = user_passkeys.user_id\n WHERE credential_id = $1\n ", "describe": { "columns": [ { @@ -40,8 +40,8 @@ }, { "ordinal": 7, - "name": "account_standing: AccountStanding", - "type_info": "Varchar" + "name": "account_locked", + "type_info": "Bool" } ], "parameters": { @@ -60,5 +60,5 @@ false ] }, - "hash": "8cbb892dae7d23e27969d53f64036783dac56fb576672161dc66ddb44c059be1" + "hash": "de703148611b2cdc3b5743046098892405dd4df00fa9548c643ea8e33e4f06df" } diff --git a/apps/labrinth/migrations/20260904120000_account_locked.sql b/apps/labrinth/migrations/20260904120000_account_locked.sql new file mode 100644 index 0000000000..0cc3a7d840 --- /dev/null +++ b/apps/labrinth/migrations/20260904120000_account_locked.sql @@ -0,0 +1,2 @@ +ALTER TABLE users + ADD COLUMN account_locked boolean NOT NULL DEFAULT FALSE; diff --git a/apps/labrinth/migrations/20260904120000_account_standing.sql b/apps/labrinth/migrations/20260904120000_account_standing.sql deleted file mode 100644 index 9702c00faf..0000000000 --- a/apps/labrinth/migrations/20260904120000_account_standing.sql +++ /dev/null @@ -1,2 +0,0 @@ -ALTER TABLE users - ADD COLUMN account_standing varchar NOT NULL DEFAULT 'full'; diff --git a/apps/labrinth/src/auth/mod.rs b/apps/labrinth/src/auth/mod.rs index de77655a37..0a4edcf591 100644 --- a/apps/labrinth/src/auth/mod.rs +++ b/apps/labrinth/src/auth/mod.rs @@ -9,7 +9,7 @@ pub use checks::{ }; use serde::{Deserialize, Serialize}; pub use validate::{ - StandingRequirement, check_is_moderator_from_headers, + AccountLockRequirement, check_is_moderator_from_headers, get_user_from_bearer_token, get_user_from_headers, }; diff --git a/apps/labrinth/src/auth/oauth/mod.rs b/apps/labrinth/src/auth/oauth/mod.rs index 1983234a04..8ff7ad7a5f 100644 --- a/apps/labrinth/src/auth/oauth/mod.rs +++ b/apps/labrinth/src/auth/oauth/mod.rs @@ -2,7 +2,7 @@ use std::fmt::Write; use crate::auth::oauth::uris::{OAuthRedirectUris, ValidatedRedirectUri}; use crate::auth::validate::extract_authorization_header; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::flow_item::DBFlow; use crate::database::models::oauth_client_authorization_item::DBOAuthClientAuthorization; @@ -83,7 +83,7 @@ pub async fn init_oauth( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .1; @@ -354,7 +354,7 @@ pub async fn accept_or_reject_client_scopes( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .1; diff --git a/apps/labrinth/src/auth/validate.rs b/apps/labrinth/src/auth/validate.rs index 9d20992a35..03880fc7b4 100644 --- a/apps/labrinth/src/auth/validate.rs +++ b/apps/labrinth/src/auth/validate.rs @@ -3,7 +3,7 @@ use crate::auth::AuthenticationError; use crate::database::models::{DBUser, user_item}; use crate::env::ENV; use crate::models::pats::Scopes; -use crate::models::users::{AccountStanding, User}; +use crate::models::users::User; use crate::queue::session::AuthQueue; use crate::routes::internal::session::get_session_metadata; use actix_web::HttpRequest; @@ -11,26 +11,21 @@ use actix_web::http::header::{AUTHORIZATION, HeaderValue}; use chrono::Utc; use xredis::RedisPool; -/// Required account standing, independent of token scopes and user role. +/// Account lock requirement, independent of token scopes and user role. #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum StandingRequirement { - Full, +pub enum AccountLockRequirement { None, + NotLocked, } -impl StandingRequirement { +impl AccountLockRequirement { pub fn check( self, - standing: AccountStanding, + account_locked: bool, ) -> Result<(), AuthenticationError> { - match (self, standing) { - (Self::Full, AccountStanding::Locked) => { - Err(AuthenticationError::AccountLocked) - } - (Self::Full, AccountStanding::Full) - | (Self::None, AccountStanding::Full | AccountStanding::Locked) => { - Ok(()) - } + match (self, account_locked) { + (Self::NotLocked, true) => Err(AuthenticationError::AccountLocked), + (Self::NotLocked, false) | (Self::None, _) => Ok(()), } } } @@ -41,7 +36,7 @@ pub async fn get_maybe_user_from_headers<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, - standing_requirement: StandingRequirement, + account_lock_requirement: AccountLockRequirement, ) -> Result, AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -58,7 +53,7 @@ where redis, session_queue, false, - standing_requirement, + account_lock_requirement, ) .await? else { @@ -78,7 +73,7 @@ pub async fn get_full_user_from_headers<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, - standing_requirement: StandingRequirement, + account_lock_requirement: AccountLockRequirement, ) -> Result<(Scopes, DBUser), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -90,7 +85,7 @@ where redis, session_queue, false, - standing_requirement, + account_lock_requirement, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -109,7 +104,7 @@ pub async fn get_user_from_headers<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, - standing_requirement: StandingRequirement, + account_lock_requirement: AccountLockRequirement, ) -> Result<(Scopes, User), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -120,7 +115,7 @@ where redis, session_queue, required_scopes, - standing_requirement, + account_lock_requirement, ) .await?; @@ -134,7 +129,7 @@ pub async fn get_user_from_bearer_token<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, allow_expired: bool, - standing_requirement: StandingRequirement, + account_lock_requirement: AccountLockRequirement, ) -> Result<(Scopes, User), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -146,7 +141,7 @@ where redis, session_queue, allow_expired, - standing_requirement, + account_lock_requirement, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -161,7 +156,7 @@ pub async fn get_user_record_from_bearer_token<'a, 'b, E>( redis: &RedisPool, session_queue: &AuthQueue, allow_expired: bool, - standing_requirement: StandingRequirement, + account_lock_requirement: AccountLockRequirement, ) -> Result, AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -263,7 +258,7 @@ where }; if let Some((_, user)) = &possible_user { - standing_requirement.check(user.account_standing)?; + account_lock_requirement.check(user.account_locked)?; } Ok(possible_user) @@ -291,7 +286,7 @@ pub async fn check_is_moderator_from_headers<'a, 'b, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, - standing_requirement: StandingRequirement, + account_lock_requirement: AccountLockRequirement, ) -> Result where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -302,7 +297,7 @@ where redis, session_queue, required_scopes, - standing_requirement, + account_lock_requirement, ) .await? .1; diff --git a/apps/labrinth/src/database/models/oauth_token_item.rs b/apps/labrinth/src/database/models/oauth_token_item.rs index 14abc14ad6..7a1777e6e7 100644 --- a/apps/labrinth/src/database/models/oauth_token_item.rs +++ b/apps/labrinth/src/database/models/oauth_token_item.rs @@ -75,7 +75,7 @@ impl DBOAuthAccessToken { SELECT $1, $2, $3, $4, $5 FROM oauth_client_authorizations auths JOIN users ON users.id = auths.user_id - WHERE auths.id = $2 AND users.account_standing = 'full' + WHERE auths.id = $2 AND NOT users.account_locked RETURNING created, expires ", self.id.0, diff --git a/apps/labrinth/src/database/models/passkey_item.rs b/apps/labrinth/src/database/models/passkey_item.rs index dab1e0cc26..ec5bc47354 100644 --- a/apps/labrinth/src/database/models/passkey_item.rs +++ b/apps/labrinth/src/database/models/passkey_item.rs @@ -1,7 +1,6 @@ use super::ids::*; use crate::database::PgTransaction; use crate::database::models::DatabaseError; -use crate::models::users::AccountStanding; use chrono::{DateTime, Utc}; use futures::TryStreamExt; use serde::{Deserialize, Serialize}; @@ -50,7 +49,7 @@ impl DBPasskey { pub async fn get_by_credential_id<'a, E>( credential_id: &[u8], exec: E, - ) -> Result, DatabaseError> + ) -> Result, DatabaseError> where E: crate::database::Executor<'a, Database = sqlx::Postgres>, { @@ -59,7 +58,7 @@ impl DBPasskey { SELECT user_passkeys.id, user_id, name, credential_id, passkey AS "passkey: sqlx::types::Json", last_used, created_at, - users.account_standing AS "account_standing: AccountStanding" + users.account_locked FROM user_passkeys JOIN users ON users.id = user_passkeys.user_id WHERE credential_id = $1 @@ -79,7 +78,7 @@ impl DBPasskey { created_at: x.created_at, last_used: x.last_used, }, - x.account_standing, + x.account_locked, ) }); diff --git a/apps/labrinth/src/database/models/user_item.rs b/apps/labrinth/src/database/models/user_item.rs index 4baba946c8..805dcd06e7 100644 --- a/apps/labrinth/src/database/models/user_item.rs +++ b/apps/labrinth/src/database/models/user_item.rs @@ -8,7 +8,7 @@ use crate::database::{PgTransaction, models}; use crate::models::billing::ChargeStatus; use crate::models::projects::ProjectStatus; use crate::models::threads::MessageBody; -use crate::models::users::{AccountStanding, Badges}; +use crate::models::users::Badges; use crate::util::error::Context; use ariadne::ids::base62_impl::{parse_base62, to_base62}; use chrono::{DateTime, Utc}; @@ -52,7 +52,7 @@ pub struct DBUser { pub bio: Option, pub created: DateTime, pub role: String, - pub account_standing: AccountStanding, + pub account_locked: bool, pub badges: Badges, #[serde(default)] pub campaign_pride_26: Option, @@ -91,7 +91,7 @@ impl DBUser { github_id, discord_id, gitlab_id, google_id, steam_id, microsoft_id, email_verified, password, paypal_id, paypal_country, paypal_email, venmo_handle, stripe_customer_id, allow_friend_requests, is_subscribed_to_newsletter, - eligibility_verified_at, account_standing + eligibility_verified_at, account_locked ) VALUES ( $1, $2, $3, $4, $5, @@ -124,7 +124,7 @@ impl DBUser { self.allow_friend_requests, self.is_subscribed_to_newsletter, self.eligibility_verified_at, - self.account_standing.as_str(), + self.account_locked, ) .execute(&mut *transaction) .await?; @@ -207,7 +207,7 @@ impl DBUser { r#" SELECT id, email, avatar_url, raw_avatar_url, username, bio, - created, role, badges, account_standing AS "account_standing: AccountStanding", + created, role, badges, account_locked, ( SELECT MAX(campaign_donations.donated_at) FROM campaign_donations @@ -246,7 +246,7 @@ impl DBUser { bio: u.bio, created: u.created, role: u.role, - account_standing: u.account_standing, + account_locked: u.account_locked, badges: Badges::from_bits(u.badges as u64).unwrap_or_default(), campaign_pride_26: u .campaign_pride_26_last_donated_at diff --git a/apps/labrinth/src/database/models/users_redeemals.rs b/apps/labrinth/src/database/models/users_redeemals.rs index 38713cfb26..a130a5b9d9 100644 --- a/apps/labrinth/src/database/models/users_redeemals.rs +++ b/apps/labrinth/src/database/models/users_redeemals.rs @@ -1,5 +1,4 @@ use crate::database::models::DBUserId; -use crate::models::users::AccountStanding; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use sqlx::{query, query_scalar}; @@ -245,7 +244,7 @@ impl UserRedeemal { #[derive(Debug)] pub struct RedeemalLookupFields { pub user_id: DBUserId, - pub account_standing: AccountStanding, + pub account_locked: bool, pub redeemal_status: Option, } @@ -269,7 +268,7 @@ impl RedeemalLookupFields { r#" SELECT users.id, - users.account_standing AS "account_standing: AccountStanding", + users.account_locked, users_redeemals.status AS "status: Option" FROM users @@ -293,7 +292,7 @@ impl RedeemalLookupFields { Ok(maybe_row.map(|row| RedeemalLookupFields { user_id: DBUserId(row.id), - account_standing: row.account_standing, + account_locked: row.account_locked, redeemal_status: row .status .as_deref() diff --git a/apps/labrinth/src/models/v2/user.rs b/apps/labrinth/src/models/v2/user.rs index aaf711f9e0..c309a3e2ce 100644 --- a/apps/labrinth/src/models/v2/user.rs +++ b/apps/labrinth/src/models/v2/user.rs @@ -1,6 +1,6 @@ use crate::{ auth::AuthProvider, - models::users::{AccountStanding, Badges, Role, UserPayoutData}, + models::users::{Badges, Role, UserPayoutData}, }; use ariadne::ids::UserId; use chrono::{DateTime, Utc}; @@ -16,7 +16,7 @@ pub struct LegacyUser { pub created: DateTime, pub role: Role, #[serde(skip_serializing_if = "Option::is_none")] - pub account_standing: Option, + pub account_locked: Option, pub badges: Badges, pub auth_providers: Option>, // this was changed in v3, but not changes ones we want to keep out of v2 @@ -41,7 +41,7 @@ impl From for LegacyUser { bio: data.bio, created: data.created, role: data.role, - account_standing: data.account_standing, + account_locked: data.account_locked, badges: data.badges, payout_data: data.payout_data, auth_providers: data.auth_providers, diff --git a/apps/labrinth/src/models/v3/users.rs b/apps/labrinth/src/models/v3/users.rs index 8e82a88266..aac1b3dd4a 100644 --- a/apps/labrinth/src/models/v3/users.rs +++ b/apps/labrinth/src/models/v3/users.rs @@ -6,35 +6,6 @@ use chrono::{DateTime, Utc}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; -#[derive( - Debug, - Clone, - Copy, - Default, - PartialEq, - Eq, - Serialize, - Deserialize, - sqlx::Decode, - utoipa::ToSchema, -)] -#[serde(rename_all = "lowercase")] -#[sqlx(rename_all = "lowercase")] -pub enum AccountStanding { - #[default] - Full, - Locked, -} - -impl AccountStanding { - pub fn as_str(&self) -> &'static str { - match self { - Self::Full => "full", - Self::Locked => "locked", - } - } -} - pub const DELETED_USER: UserId = UserId(127155982985829); bitflags::bitflags! { @@ -85,7 +56,7 @@ pub struct User { pub created: DateTime, pub role: Role, #[serde(skip_serializing_if = "Option::is_none")] - pub account_standing: Option, + pub account_locked: Option, pub badges: Badges, pub campaigns: UserCampaigns, @@ -144,7 +115,7 @@ impl From for User { bio: data.bio, created: data.created, role: Role::from_string(&data.role), - account_standing: None, + account_locked: None, badges: data.badges, campaigns: UserCampaigns { pride_26: data.campaign_pride_26, @@ -209,7 +180,7 @@ impl User { bio: db_user.bio, created: db_user.created, role: Role::from_string(&db_user.role), - account_standing: Some(db_user.account_standing), + account_locked: Some(db_user.account_locked), badges: db_user.badges, campaigns: UserCampaigns { pride_26: db_user.campaign_pride_26, diff --git a/apps/labrinth/src/routes/analytics.rs b/apps/labrinth/src/routes/analytics.rs index a94a93dd6a..88c720ff45 100644 --- a/apps/labrinth/src/routes/analytics.rs +++ b/apps/labrinth/src/routes/analytics.rs @@ -1,4 +1,4 @@ -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::DBProject; use crate::env::ENV; @@ -80,7 +80,7 @@ pub async fn page_view_ingest( &redis, &session_queue, Scopes::empty(), - StandingRequirement::None, + AccountLockRequirement::None, ) .await .ok(); @@ -211,7 +211,7 @@ pub async fn playtime_ingest( &redis, &session_queue, Scopes::PERFORM_ANALYTICS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -298,7 +298,7 @@ pub async fn minecraft_server_play_ingest( &redis, &session_queue, Scopes::empty(), - StandingRequirement::None, + AccountLockRequirement::None, ) .await .map(|(_, user)| user) diff --git a/apps/labrinth/src/routes/internal/admin.rs b/apps/labrinth/src/routes/internal/admin.rs index 9f391c1819..1f52e4e020 100644 --- a/apps/labrinth/src/routes/internal/admin.rs +++ b/apps/labrinth/src/routes/internal/admin.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::auth::validate::get_user_record_from_bearer_token; use crate::database::PgPool; use crate::models::analytics::{Download, DownloadReason}; @@ -169,7 +169,7 @@ pub async fn count_download( &redis, &session_queue, false, - StandingRequirement::None, + AccountLockRequirement::None, ) .await .ok() diff --git a/apps/labrinth/src/routes/internal/affiliate.rs b/apps/labrinth/src/routes/internal/affiliate.rs index 74dcdbb687..494121de5d 100644 --- a/apps/labrinth/src/routes/internal/affiliate.rs +++ b/apps/labrinth/src/routes/internal/affiliate.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use std::{collections::HashMap, net::Ipv4Addr, sync::Arc}; use xredis::RedisPool; @@ -60,7 +60,7 @@ pub async fn ingest_click( &redis, &session_queue, Scopes::empty(), - StandingRequirement::None, + AccountLockRequirement::None, ) .await .map(|(_, user)| user) @@ -160,7 +160,7 @@ pub async fn get_all( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -217,7 +217,7 @@ pub async fn create( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -303,7 +303,7 @@ pub async fn get( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -348,7 +348,7 @@ pub async fn delete( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -406,7 +406,7 @@ pub async fn patch( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/analytics_event.rs b/apps/labrinth/src/routes/internal/analytics_event.rs index a4a4361d9f..45daf4f962 100644 --- a/apps/labrinth/src/routes/internal/analytics_event.rs +++ b/apps/labrinth/src/routes/internal/analytics_event.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use actix_web::{HttpRequest, delete, patch, post, web}; use chrono::{DateTime, Utc}; use eyre::eyre; @@ -56,7 +56,7 @@ pub async fn analytics_event_create( &redis, &session_queue, Scopes::empty(), - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -118,7 +118,7 @@ pub async fn analytics_event_edit( &redis, &session_queue, Scopes::empty(), - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -170,7 +170,7 @@ pub async fn analytics_event_delete( &redis, &session_queue, Scopes::empty(), - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/attribution.rs b/apps/labrinth/src/routes/internal/attribution.rs index 8db81fcd24..ec1c6e7b06 100644 --- a/apps/labrinth/src/routes/internal/attribution.rs +++ b/apps/labrinth/src/routes/internal/attribution.rs @@ -5,7 +5,8 @@ use eyre::eyre; use serde::{Deserialize, Serialize}; use crate::auth::{ - StandingRequirement, check_is_moderator_from_headers, get_user_from_headers, + AccountLockRequirement, check_is_moderator_from_headers, + get_user_from_headers, }; use crate::database::PgPool; use crate::database::models::{ @@ -127,7 +128,7 @@ pub async fn scan( &redis, &session_queue, Scopes::VERSION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -243,7 +244,7 @@ async fn force_scan_file( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -327,7 +328,7 @@ pub async fn list( &redis, &session_queue, Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -661,7 +662,7 @@ pub async fn update_group( &redis, &session_queue, Scopes::VERSION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -750,7 +751,7 @@ pub async fn delete_groups( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("deleting database records for `delete_groups`")?; @@ -790,7 +791,7 @@ pub async fn delete_all_groups( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("deleting database records for `delete_all_groups`")?; @@ -911,7 +912,7 @@ pub async fn assign( &redis, &session_queue, Scopes::VERSION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1075,7 +1076,7 @@ pub async fn split( &redis, &session_queue, Scopes::VERSION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/billing.rs b/apps/labrinth/src/routes/internal/billing.rs index 241081bb3c..98d8bf04b7 100644 --- a/apps/labrinth/src/routes/internal/billing.rs +++ b/apps/labrinth/src/routes/internal/billing.rs @@ -1,6 +1,6 @@ use self::payments::*; use self::update_subscriptions::*; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::models::charge_item::DBCharge; use crate::database::models::ids::DBUserSubscriptionId; use crate::database::models::notification_item::NotificationBuilder; @@ -139,7 +139,7 @@ pub async fn subscriptions( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -220,7 +220,7 @@ pub async fn refund_charge( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -512,7 +512,7 @@ pub async fn reprocess_charge_tax( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -714,7 +714,7 @@ pub async fn edit_subscription( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1213,7 +1213,7 @@ pub async fn user_customer( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1265,7 +1265,7 @@ pub async fn charges( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1332,7 +1332,7 @@ pub async fn add_payment_method_flow( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1395,7 +1395,7 @@ pub async fn edit_payment_method( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1470,7 +1470,7 @@ pub async fn remove_payment_method( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1572,7 +1572,7 @@ pub async fn payment_methods( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1750,7 +1750,7 @@ pub async fn initiate_payment( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2830,7 +2830,7 @@ pub async fn credit( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/delphi/mod.rs b/apps/labrinth/src/routes/internal/delphi/mod.rs index c01292ea8c..de28221741 100644 --- a/apps/labrinth/src/routes/internal/delphi/mod.rs +++ b/apps/labrinth/src/routes/internal/delphi/mod.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use std::{collections::HashMap, fmt::Write, time::Instant}; use xredis::RedisPool; @@ -335,7 +335,7 @@ pub async fn _run( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -362,7 +362,7 @@ pub async fn version( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -395,7 +395,7 @@ pub async fn issue_type_schema( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("deserializing HTTP response")?; diff --git a/apps/labrinth/src/routes/internal/external_notifications.rs b/apps/labrinth/src/routes/internal/external_notifications.rs index 04e17e9c9c..2c1f6ee859 100644 --- a/apps/labrinth/src/routes/internal/external_notifications.rs +++ b/apps/labrinth/src/routes/internal/external_notifications.rs @@ -2,7 +2,7 @@ use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::collections::HashMap; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::ids::{DBNotificationId, DBUserId}; use crate::database::models::notification_item::DBNotification; @@ -375,7 +375,7 @@ pub async fn send_custom_email( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/flows.rs b/apps/labrinth/src/routes/internal/flows.rs index 7c6b89ad95..3697de2d27 100644 --- a/apps/labrinth/src/routes/internal/flows.rs +++ b/apps/labrinth/src/routes/internal/flows.rs @@ -2,7 +2,7 @@ use crate::auth::validate::{ get_full_user_from_headers, get_user_record_from_bearer_token, }; use crate::auth::{ - AuthProvider, AuthenticationError, StandingRequirement, + AccountLockRequirement, AuthProvider, AuthenticationError, get_user_from_headers, }; use crate::database::PgPool; @@ -17,7 +17,7 @@ use crate::models::error::ApiError as ApiErrorResponse; use crate::models::ids::PasskeyId; use crate::models::notifications::NotificationBody; use crate::models::pats::Scopes; -use crate::models::users::{AccountStanding, Badges, Role}; +use crate::models::users::{Badges, Role}; use crate::queue::email::EmailQueue; use crate::queue::session::AuthQueue; use crate::routes::ApiError; @@ -255,7 +255,7 @@ impl TempUser { bio: self.bio, created: Utc::now(), role: Role::Developer.to_string(), - account_standing: AccountStanding::Full, + account_locked: false, badges: Badges::default(), campaign_pride_26: None, allow_friend_requests: true, @@ -1108,7 +1108,7 @@ pub async fn init( &redis, &session_queue, false, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .map(|(_scopes, user)| user.id) @@ -1148,7 +1148,7 @@ pub async fn init( &redis, &session_queue, false, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -1298,7 +1298,7 @@ pub async fn auth_callback( " UPDATE users SET paypal_country = $1, paypal_email = $2, paypal_id = $3 - WHERE id = $4 AND account_standing = 'full' + WHERE id = $4 AND NOT account_locked ", oauth_user.country, oauth_user.email, @@ -1337,7 +1337,7 @@ pub async fn auth_callback( let user = DBUser::get_id(id, &**client, &redis) .await? .ok_or(AuthenticationError::InvalidCredentials)?; - StandingRequirement::Full.check(user.account_standing)?; + AccountLockRequirement::NotLocked.check(user.account_locked)?; provider .update_user_id(id, Some(&oauth_user.id), &mut transaction) @@ -1368,7 +1368,7 @@ pub async fn auth_callback( .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; - StandingRequirement::Full.check(user.account_standing)?; + AccountLockRequirement::NotLocked.check(user.account_locked)?; if user.totp_secret.is_some() { let flow = DBFlow::Login2FA { user_id: user.id } @@ -1661,7 +1661,7 @@ pub async fn discord_community_link( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1734,7 +1734,7 @@ pub async fn delete_auth_provider( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2107,7 +2107,7 @@ impl ReadyAccountRegisterFlow { bio: None, created: Utc::now(), role: Role::Developer.to_string(), - account_standing: AccountStanding::Full, + account_locked: false, badges: Badges::default(), campaign_pride_26: None, allow_friend_requests: true, @@ -2324,9 +2324,9 @@ pub async fn login_password( .map_err(|_| AuthenticationError::InvalidCredentials) .wrap_auth_err("authenticating API request")?; - StandingRequirement::Full - .check(user.account_standing) - .wrap_auth_err("checking account standing")?; + AccountLockRequirement::NotLocked + .check(user.account_locked) + .wrap_auth_err("checking account lock")?; if user.totp_secret.is_some() { let flow = DBFlow::Login2FA { user_id: user.id } @@ -2469,9 +2469,9 @@ pub async fn login_2fa( .ok_or_else(|| AuthenticationError::InvalidCredentials) .wrap_auth_err("fetching user from database")?; - StandingRequirement::Full - .check(user.account_standing) - .wrap_auth_err("checking account standing")?; + AccountLockRequirement::NotLocked + .check(user.account_locked) + .wrap_auth_err("checking account lock")?; let mut transaction = pool .begin() @@ -2542,7 +2542,7 @@ pub async fn begin_2fa_flow( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2604,7 +2604,7 @@ pub async fn finish_2fa_flow( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2753,7 +2753,7 @@ pub async fn remove_2fa( &redis, &session_queue, false, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2927,13 +2927,13 @@ pub async fn reset_password_begin( if let Some(DBUser { id: user_id, email: user_email, - account_standing, + account_locked, .. }) = user { - StandingRequirement::Full - .check(account_standing) - .wrap_auth_err("checking account standing")?; + AccountLockRequirement::NotLocked + .check(account_locked) + .wrap_auth_err("checking account lock")?; let flow = DBFlow::ForgotPassword { user_id } .insert(Duration::hours(24), &redis) @@ -3024,7 +3024,7 @@ pub async fn change_password( &redis, &session_queue, false, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3059,9 +3059,9 @@ pub async fn change_password( user }; - StandingRequirement::Full - .check(user.account_standing) - .wrap_auth_err("checking account standing")?; + AccountLockRequirement::NotLocked + .check(user.account_locked) + .wrap_auth_err("checking account lock")?; let mut transaction = pool .begin() @@ -3210,7 +3210,7 @@ pub async fn set_email( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3341,7 +3341,7 @@ pub async fn resend_verify_email( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3436,9 +3436,9 @@ pub async fn verify_email( .ok_or_else(|| AuthenticationError::InvalidCredentials) .wrap_auth_err("fetching user from database")?; - StandingRequirement::Full - .check(user.account_standing) - .wrap_auth_err("checking account standing")?; + AccountLockRequirement::NotLocked + .check(user.account_locked) + .wrap_auth_err("checking account lock")?; if user.email != Some(confirm_email) { return Err(ApiError::Request(eyre::eyre!( @@ -3510,7 +3510,7 @@ pub async fn subscribe_newsletter( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3563,7 +3563,7 @@ pub async fn get_newsletter_subscription_status( &redis, &session_queue, Scopes::USER_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3618,7 +3618,7 @@ pub async fn register_passkey_start( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3722,7 +3722,7 @@ pub async fn register_passkey_finish( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3884,15 +3884,15 @@ pub async fn authenticate_passkey_finish( if let Some(DBFlow::AuthenticatePasskey { state }) = flow { let credential_id = response.credential.get_credential_id(); - let (db_passkey, account_standing) = + let (db_passkey, account_locked) = DBPasskey::get_by_credential_id(credential_id, &**pool) .await .wrap_internal_err("failed to fetch passkey")? .wrap_request_err_with(|| "passkey not found")?; - StandingRequirement::Full - .check(account_standing) - .wrap_auth_err("checking account standing")?; + AccountLockRequirement::NotLocked + .check(account_locked) + .wrap_auth_err("checking account lock")?; let mut transaction = pool .begin() @@ -4009,7 +4009,7 @@ pub async fn list_passkeys( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -4064,7 +4064,7 @@ pub async fn rename_passkey( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -4125,7 +4125,7 @@ pub async fn delete_passkey( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/gdpr.rs b/apps/labrinth/src/routes/internal/gdpr.rs index ef1ef5f0e5..78ab9f6fcf 100644 --- a/apps/labrinth/src/routes/internal/gdpr.rs +++ b/apps/labrinth/src/routes/internal/gdpr.rs @@ -1,4 +1,4 @@ -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::models::pats::Scopes; use crate::queue::session::AuthQueue; @@ -30,7 +30,7 @@ pub async fn export( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/medal.rs b/apps/labrinth/src/routes/internal/medal.rs index 8a960b523d..7a94f5ac16 100644 --- a/apps/labrinth/src/routes/internal/medal.rs +++ b/apps/labrinth/src/routes/internal/medal.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::database::PgPool; use crate::util::error::Context as _; use actix_web::{HttpResponse, post, web}; @@ -53,9 +53,9 @@ pub async fn verify( match maybe_fields { None => Err(ApiError::NotFound(eyre::eyre!("resource not found"))), Some(fields) => { - StandingRequirement::Full - .check(fields.account_standing) - .wrap_auth_err("checking account standing")?; + AccountLockRequirement::NotLocked + .check(fields.account_locked) + .wrap_auth_err("checking account lock")?; Ok(HttpResponse::Ok().json(VerifyResponse { user_id: fields.user_id.into(), redeemed: fields.redeemal_status.is_some(), @@ -98,9 +98,9 @@ pub async fn redeem( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); } Some(fields) => { - StandingRequirement::Full - .check(fields.account_standing) - .wrap_auth_err("checking account standing")?; + AccountLockRequirement::NotLocked + .check(fields.account_locked) + .wrap_auth_err("checking account lock")?; if fields.redeemal_status.is_some() { return Err(ApiError::Conflict(eyre::eyre!( "User already redeemed this offer", diff --git a/apps/labrinth/src/routes/internal/moderation/external_license.rs b/apps/labrinth/src/routes/internal/moderation/external_license.rs index 415488b14a..1eb187521f 100644 --- a/apps/labrinth/src/routes/internal/moderation/external_license.rs +++ b/apps/labrinth/src/routes/internal/moderation/external_license.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::collections::HashMap; @@ -358,7 +358,7 @@ pub async fn search( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating external license search")?; @@ -432,7 +432,7 @@ pub async fn lookup( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -473,7 +473,7 @@ pub async fn get_by_sha1( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -511,7 +511,7 @@ pub async fn get_by_sha1_bulk( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -572,7 +572,7 @@ async fn upsert_file_license( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -686,7 +686,7 @@ pub async fn update_license( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/mod.rs b/apps/labrinth/src/routes/internal/moderation/mod.rs index 247f0673f6..e41cab0868 100644 --- a/apps/labrinth/src/routes/internal/moderation/mod.rs +++ b/apps/labrinth/src/routes/internal/moderation/mod.rs @@ -1,5 +1,5 @@ use super::ApiError; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::database::models::moderation_external_item; @@ -263,7 +263,7 @@ pub async fn get_projects_internal( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -748,7 +748,7 @@ pub async fn get_project_ids( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1098,7 +1098,7 @@ pub async fn get_project_meta( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1262,7 +1262,7 @@ pub async fn set_project_meta( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1369,7 +1369,7 @@ pub async fn acquire_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1434,7 +1434,7 @@ pub async fn override_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1486,7 +1486,7 @@ pub async fn get_lock_status( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1554,7 +1554,7 @@ pub async fn release_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1620,7 +1620,7 @@ pub async fn release_lock_beacon( &redis, &session_queue, false, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1677,7 +1677,7 @@ pub async fn delete_all_locks( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1719,7 +1719,7 @@ pub async fn get_user_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1764,7 +1764,7 @@ pub async fn get_users_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1822,7 +1822,7 @@ pub async fn get_organization_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1871,7 +1871,7 @@ pub async fn get_organizations_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review.rs b/apps/labrinth/src/routes/internal/moderation/tech_review.rs index b9e0b2f744..88c5857a15 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use std::{collections::HashMap, fmt}; use xredis::RedisPool; @@ -245,7 +245,7 @@ pub async fn get_issue( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -313,7 +313,7 @@ pub async fn get_report( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -764,7 +764,7 @@ pub async fn search_projects( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -997,7 +997,7 @@ pub async fn get_project_report( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1101,7 +1101,7 @@ pub async fn submit_report( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1309,7 +1309,7 @@ pub async fn update_issue_details( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("updating database records for `update_issue_details`")?; @@ -1463,7 +1463,7 @@ pub async fn update_global_issue_details( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err( @@ -1603,7 +1603,7 @@ pub async fn add_report( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("inserting database records for `add_report`")?; @@ -1688,7 +1688,7 @@ pub async fn get_user_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1729,7 +1729,7 @@ pub async fn get_users_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1785,7 +1785,7 @@ pub async fn get_organization_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1827,7 +1827,7 @@ pub async fn get_organizations_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs index f3df78ea2b..355453f657 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use actix_web::{HttpRequest, post, web}; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -148,7 +148,7 @@ pub async fn search_global_issue_details( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating global issue search")?; @@ -373,7 +373,7 @@ pub async fn get_global_issue_detail( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating global issue detail request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs index eeb059133b..29abdeeefa 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use actix_web::{HttpRequest, delete, get, post, put, web}; use chrono::{DateTime, Utc}; use eyre::eyre; @@ -194,7 +194,7 @@ pub async fn test_rule( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -251,7 +251,7 @@ pub async fn get_rules( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -429,7 +429,7 @@ pub async fn get_rule_affected_details( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -540,7 +540,7 @@ pub async fn create_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -629,7 +629,7 @@ pub async fn update_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -713,7 +713,7 @@ pub async fn delete_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs index 020cd0f88c..b88a59c0c3 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use std::collections::{BTreeMap, HashMap}; use actix_web::{HttpRequest, HttpResponse, get, post, web}; @@ -162,7 +162,7 @@ pub async fn get_rule_schema( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -208,7 +208,7 @@ pub async fn get_detail_rule_input( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -357,7 +357,7 @@ pub async fn scan_rules( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/pats.rs b/apps/labrinth/src/routes/internal/pats.rs index de71b541e0..63fcb18df1 100644 --- a/apps/labrinth/src/routes/internal/pats.rs +++ b/apps/labrinth/src/routes/internal/pats.rs @@ -2,7 +2,7 @@ use crate::database; use crate::database::models::generate_pat_id; use crate::util::error::Context as _; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::routes::ApiError; use actix_web::web::{self, Data}; @@ -53,7 +53,7 @@ pub async fn get_pats( &redis, &session_queue, Scopes::PAT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -130,7 +130,7 @@ pub async fn create_pat( &redis, &session_queue, Scopes::PAT_CREATE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -242,7 +242,7 @@ pub async fn edit_pat( &redis, &session_queue, Scopes::PAT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -361,7 +361,7 @@ pub async fn delete_pat( &redis, &session_queue, Scopes::PAT_DELETE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/privacy.rs b/apps/labrinth/src/routes/internal/privacy.rs index 79836f3fdd..301fadcc2f 100644 --- a/apps/labrinth/src/routes/internal/privacy.rs +++ b/apps/labrinth/src/routes/internal/privacy.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::database::PgPool; use crate::database::models::blocked_user_item::DBBlockedUser; use crate::database::models::friend_item::DBFriend; @@ -51,9 +51,9 @@ pub async fn invite_privacy_status( .wrap_internal_err("resolving user ids")?; let user = user.wrap_not_found_err("user not found")?; - StandingRequirement::Full - .check(user.account_standing) - .wrap_auth_err("checking inviter account standing")?; + AccountLockRequirement::NotLocked + .check(user.account_locked) + .wrap_auth_err("checking inviter account lock")?; let user_id = user.id; let target_id = target.wrap_not_found_err("target not found")?.id; diff --git a/apps/labrinth/src/routes/internal/server_ping.rs b/apps/labrinth/src/routes/internal/server_ping.rs index ba75010315..c21a908bc0 100644 --- a/apps/labrinth/src/routes/internal/server_ping.rs +++ b/apps/labrinth/src/routes/internal/server_ping.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use std::time::Duration; use xredis::RedisPool; @@ -44,7 +44,7 @@ pub async fn ping_minecraft_java( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/session.rs b/apps/labrinth/src/routes/internal/session.rs index b18aaa172f..4dae6744e5 100644 --- a/apps/labrinth/src/routes/internal/session.rs +++ b/apps/labrinth/src/routes/internal/session.rs @@ -1,6 +1,6 @@ use crate::auth::validate::get_user_from_bearer_token; use crate::auth::{ - AuthenticationError, StandingRequirement, get_user_from_headers, + AccountLockRequirement, AuthenticationError, get_user_from_headers, }; use crate::database::models::DBUserId; use crate::database::models::session_item::DBSession; @@ -161,7 +161,7 @@ pub async fn list( &redis, &session_queue, Scopes::SESSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -218,7 +218,7 @@ pub async fn delete( &redis, &session_queue, Scopes::SESSION_DELETE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -296,7 +296,7 @@ pub async fn refresh( &redis, &session_queue, true, // Allow expired sessions, since we want to allow refreshing expired sessions - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/statuses.rs b/apps/labrinth/src/routes/internal/statuses.rs index 21cd330445..88785fc290 100644 --- a/apps/labrinth/src/routes/internal/statuses.rs +++ b/apps/labrinth/src/routes/internal/statuses.rs @@ -1,5 +1,5 @@ +use crate::auth::AccountLockRequirement; use crate::auth::AuthenticationError; -use crate::auth::StandingRequirement; use crate::auth::validate::get_user_record_from_bearer_token; use crate::database::models::friend_item::DBFriend; use crate::database::models::notification_item::DBNotification; @@ -69,7 +69,7 @@ pub async fn ws_init( &redis, &session_queue, false, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/maven.rs b/apps/labrinth/src/routes/maven.rs index 4feecbd54b..e7c7477022 100644 --- a/apps/labrinth/src/routes/maven.rs +++ b/apps/labrinth/src/routes/maven.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::auth::checks::{is_visible_project, is_visible_version}; use crate::database::PgPool; use crate::database::models::legacy_loader_fields::MinecraftGameVersion; @@ -101,7 +101,7 @@ pub async fn maven_metadata( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -340,7 +340,7 @@ pub async fn version_file( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -428,7 +428,7 @@ pub async fn version_file_sha1( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -495,7 +495,7 @@ pub async fn version_file_sha512( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/mod.rs b/apps/labrinth/src/routes/mod.rs index 6ed9c9b084..942cabc4c4 100644 --- a/apps/labrinth/src/routes/mod.rs +++ b/apps/labrinth/src/routes/mod.rs @@ -313,7 +313,7 @@ mod tests { let direct = AuthenticationError::AccountLocked; assert_eq!(direct.status_code(), StatusCode::FORBIDDEN); let wrapped = Err::<(), _>(direct) - .wrap_auth_err("checking account standing") + .wrap_auth_err("checking account lock") .unwrap_err() .wrap_err("authenticating API request"); assert_eq!(wrapped.status_code(), StatusCode::FORBIDDEN); diff --git a/apps/labrinth/src/routes/updates.rs b/apps/labrinth/src/routes/updates.rs index 5dc51d11e7..09d0cdea77 100644 --- a/apps/labrinth/src/routes/updates.rs +++ b/apps/labrinth/src/routes/updates.rs @@ -9,7 +9,7 @@ use actix_web::{HttpRequest, HttpResponse, get, web}; use serde::{Deserialize, Serialize}; use crate::auth::checks::{filter_visible_versions, is_visible_project}; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database; use crate::database::models::legacy_loader_fields::MinecraftGameVersion; use crate::models::pats::Scopes; @@ -67,7 +67,7 @@ pub async fn forge_updates( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/v2/users.rs b/apps/labrinth/src/routes/v2/users.rs index 41c37541e6..bdc79aaa93 100644 --- a/apps/labrinth/src/routes/v2/users.rs +++ b/apps/labrinth/src/routes/v2/users.rs @@ -2,7 +2,7 @@ use crate::database::PgPool; use crate::file_hosting::FileHost; use crate::models::notifications::Notification; use crate::models::projects::Project; -use crate::models::users::{AccountStanding, Badges, Role, User}; +use crate::models::users::{Badges, Role, User}; use crate::models::v2::notifications::LegacyNotification; use crate::models::v2::projects::LegacyProject; use crate::models::v2::user::LegacyUser; @@ -223,7 +223,7 @@ pub struct EditUser { #[validate(length(max = 160))] pub bio: Option>, pub role: Option, - pub account_standing: Option, + pub account_locked: Option, pub badges: Option, pub allow_friend_requests: Option, } @@ -269,7 +269,7 @@ pub async fn user_edit( username: new_user.username, bio: new_user.bio, role: new_user.role, - account_standing: new_user.account_standing, + account_locked: new_user.account_locked, badges: new_user.badges, venmo_handle: None, allow_friend_requests: new_user.allow_friend_requests, diff --git a/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs b/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs index 8f04c09a58..69de108c5f 100644 --- a/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs +++ b/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use xredis::RedisPool; @@ -80,7 +80,7 @@ pub async fn fetch_facets( &redis, &session_queue, Scopes::ANALYTICS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/analytics_get/mod.rs b/apps/labrinth/src/routes/v3/analytics_get/mod.rs index b6c0dd17db..65aa7b40a6 100644 --- a/apps/labrinth/src/routes/v3/analytics_get/mod.rs +++ b/apps/labrinth/src/routes/v3/analytics_get/mod.rs @@ -7,7 +7,7 @@ //! requests, you have to zip together M arrays of N elements //! - this makes it inconvenient to have separate endpoints -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; @@ -196,7 +196,7 @@ pub async fn fetch_analytics( &redis, &session_queue, Scopes::ANALYTICS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/blocked_users.rs b/apps/labrinth/src/routes/v3/blocked_users.rs index 267f53610b..40d497e46b 100644 --- a/apps/labrinth/src/routes/v3/blocked_users.rs +++ b/apps/labrinth/src/routes/v3/blocked_users.rs @@ -1,4 +1,4 @@ -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::DBUser; use crate::database::models::blocked_user_item::DBBlockedUser; @@ -34,7 +34,7 @@ pub async fn block_user( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -93,7 +93,7 @@ pub async fn unblock_user( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -129,7 +129,7 @@ pub async fn get_blocked_users( &redis, &session_queue, Scopes::USER_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/collections.rs b/apps/labrinth/src/routes/v3/collections.rs index d8af1e8bbc..96178e3622 100644 --- a/apps/labrinth/src/routes/v3/collections.rs +++ b/apps/labrinth/src/routes/v3/collections.rs @@ -1,6 +1,6 @@ use crate::auth::checks::is_visible_collection; use crate::auth::{ - StandingRequirement, filter_visible_collections, get_user_from_headers, + AccountLockRequirement, filter_visible_collections, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; @@ -76,7 +76,7 @@ pub async fn collection_create( &redis, &session_queue, Scopes::COLLECTION_CREATE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .1; @@ -181,7 +181,7 @@ pub async fn collections_get( &redis, &session_queue, Scopes::COLLECTION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -219,7 +219,7 @@ pub async fn collection_get( &redis, &session_queue, Scopes::COLLECTION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -271,7 +271,7 @@ pub async fn collection_edit( &redis, &session_queue, Scopes::COLLECTION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -455,7 +455,7 @@ pub async fn collection_icon_edit( &redis, &session_queue, Scopes::COLLECTION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -554,7 +554,7 @@ pub async fn delete_collection_icon( &redis, &session_queue, Scopes::COLLECTION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -626,7 +626,7 @@ pub async fn collection_delete( &redis, &session_queue, Scopes::COLLECTION_DELETE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/content/mod.rs b/apps/labrinth/src/routes/v3/content/mod.rs index 6534a2327f..6b0a6b3802 100644 --- a/apps/labrinth/src/routes/v3/content/mod.rs +++ b/apps/labrinth/src/routes/v3/content/mod.rs @@ -2,7 +2,7 @@ use super::ApiError; use crate::auth::checks::{ filter_visible_versions, is_visible_project, is_visible_version, }; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::models::ids::DBVersionId; use crate::database::models::version_item::VersionQueryResult; use crate::database::models::{DBProject, DBVersion}; @@ -54,7 +54,7 @@ pub async fn resolve_content( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/v3/disclosures.rs b/apps/labrinth/src/routes/v3/disclosures.rs index 272cb79112..02095850eb 100644 --- a/apps/labrinth/src/routes/v3/disclosures.rs +++ b/apps/labrinth/src/routes/v3/disclosures.rs @@ -6,7 +6,7 @@ use utoipa::ToSchema; use xredis::RedisPool; use crate::auth::checks::{is_team_member_project, is_visible_project}; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::{DBProject, models as db_models}; use crate::database::{PgPool, ReadOnlyPgPool}; use crate::models::disclosures::{ @@ -59,7 +59,7 @@ pub async fn get_project_disclosures( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|(_, user)| user) @@ -142,7 +142,7 @@ pub async fn modify_project_disclosures( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/friends.rs b/apps/labrinth/src/routes/v3/friends.rs index eac0665a42..266bb26ab2 100644 --- a/apps/labrinth/src/routes/v3/friends.rs +++ b/apps/labrinth/src/routes/v3/friends.rs @@ -1,4 +1,4 @@ -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::blocked_user_item::DBBlockedUser; use crate::database::models::friend_item::DBFriend; @@ -43,7 +43,7 @@ pub async fn add_friend( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -194,7 +194,7 @@ pub async fn remove_friend( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -249,7 +249,7 @@ pub async fn friends( &redis, &session_queue, Scopes::USER_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/images.rs b/apps/labrinth/src/routes/v3/images.rs index f2aea21557..bdccbcea3f 100644 --- a/apps/labrinth/src/routes/v3/images.rs +++ b/apps/labrinth/src/routes/v3/images.rs @@ -1,6 +1,6 @@ use super::threads::is_authorized_thread; use crate::auth::checks::{is_team_member_project, is_team_member_version}; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::database::models::{ @@ -69,7 +69,7 @@ pub async fn images_add( &redis, &session_queue, context.relevant_scope(), - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/limits.rs b/apps/labrinth/src/routes/v3/limits.rs index 47977fc410..40c524bede 100644 --- a/apps/labrinth/src/routes/v3/limits.rs +++ b/apps/labrinth/src/routes/v3/limits.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::database::PgPool; use crate::util::error::Context as _; use crate::{ @@ -30,7 +30,7 @@ pub async fn get_project_limits( &redis, &session_queue, Scopes::empty(), - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -55,7 +55,7 @@ pub async fn get_organization_limits( &redis, &session_queue, Scopes::empty(), - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -80,7 +80,7 @@ pub async fn get_collection_limits( &redis, &session_queue, Scopes::empty(), - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/notifications.rs b/apps/labrinth/src/routes/v3/notifications.rs index 63291704ed..63216dddf5 100644 --- a/apps/labrinth/src/routes/v3/notifications.rs +++ b/apps/labrinth/src/routes/v3/notifications.rs @@ -1,4 +1,4 @@ -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::models::ids::NotificationId; @@ -54,7 +54,7 @@ pub async fn notifications_get( &redis, &session_queue, Scopes::NOTIFICATION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -112,7 +112,7 @@ pub async fn notification_get( &redis, &session_queue, Scopes::NOTIFICATION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -164,7 +164,7 @@ pub async fn notification_read( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -236,7 +236,7 @@ pub async fn notification_delete( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -312,7 +312,7 @@ pub async fn notifications_read( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -392,7 +392,7 @@ pub async fn notifications_delete( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/oauth_clients.rs b/apps/labrinth/src/routes/v3/oauth_clients.rs index 8b5b22d905..9784642d78 100644 --- a/apps/labrinth/src/routes/v3/oauth_clients.rs +++ b/apps/labrinth/src/routes/v3/oauth_clients.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::{collections::HashSet, fmt::Display}; @@ -73,7 +73,7 @@ pub async fn get_user_clients( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -201,7 +201,7 @@ pub async fn oauth_client_create( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .1; @@ -270,7 +270,7 @@ pub async fn oauth_client_delete( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -341,7 +341,7 @@ pub async fn oauth_client_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -447,7 +447,7 @@ pub async fn oauth_client_icon_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -538,7 +538,7 @@ pub async fn oauth_client_icon_delete( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -605,7 +605,7 @@ pub async fn get_user_oauth_authorizations( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -645,7 +645,7 @@ pub async fn revoke_oauth_authorization( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/organizations.rs b/apps/labrinth/src/routes/v3/organizations.rs index 527592bced..08084507c5 100644 --- a/apps/labrinth/src/routes/v3/organizations.rs +++ b/apps/labrinth/src/routes/v3/organizations.rs @@ -4,7 +4,7 @@ use std::collections::HashMap; use super::ApiError; use crate::auth::checks::is_visible_organization; use crate::auth::{ - StandingRequirement, filter_visible_projects, get_user_from_headers, + AccountLockRequirement, filter_visible_projects, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; @@ -63,7 +63,7 @@ pub async fn organization_projects_get( &redis, &session_queue, Scopes::ORGANIZATION_READ | Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -139,7 +139,7 @@ pub async fn organization_create( &redis, &session_queue, Scopes::ORGANIZATION_CREATE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .1; @@ -247,7 +247,7 @@ pub async fn organization_get( &redis, &session_queue, Scopes::ORGANIZATION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -335,7 +335,7 @@ pub async fn organization_notes_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -460,7 +460,7 @@ pub async fn organizations_get( &redis, &session_queue, Scopes::ORGANIZATION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -566,7 +566,7 @@ pub async fn organizations_edit( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -749,7 +749,7 @@ pub async fn organization_delete( &redis, &session_queue, Scopes::ORGANIZATION_DELETE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -939,7 +939,7 @@ pub async fn organization_projects_add( &redis, &session_queue, Scopes::PROJECT_WRITE | Scopes::ORGANIZATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1122,7 +1122,7 @@ pub async fn organization_projects_remove( &redis, &session_queue, Scopes::PROJECT_WRITE | Scopes::ORGANIZATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1336,7 +1336,7 @@ pub async fn organization_icon_edit( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1454,7 +1454,7 @@ pub async fn delete_organization_icon( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/payouts.rs b/apps/labrinth/src/routes/v3/payouts.rs index 52ee6eef0e..ffcce07bb4 100644 --- a/apps/labrinth/src/routes/v3/payouts.rs +++ b/apps/labrinth/src/routes/v3/payouts.rs @@ -1,6 +1,6 @@ use crate::auth::validate::get_user_record_from_bearer_token; use crate::auth::{ - AuthenticationError, StandingRequirement, get_user_from_headers, + AccountLockRequirement, AuthenticationError, get_user_from_headers, }; use crate::database::PgPool; use crate::database::models::DBUserId; @@ -72,7 +72,7 @@ pub async fn post_compliance_form( &redis, &session_queue, Scopes::PAYOUTS_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -524,7 +524,7 @@ pub async fn calculate_fees( &redis, &session_queue, false, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -564,7 +564,7 @@ pub async fn create_payout( &redis, &session_queue, false, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -792,7 +792,7 @@ pub async fn transaction_history( &redis, &session_queue, Scopes::PAYOUTS_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -884,7 +884,7 @@ pub async fn cancel_payout( &redis, &session_queue, Scopes::PAYOUTS_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1068,7 +1068,7 @@ pub async fn get_balance( &redis, &session_queue, Scopes::PAYOUTS_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/project_creation.rs b/apps/labrinth/src/routes/v3/project_creation.rs index 5cd9507780..4d6e273639 100644 --- a/apps/labrinth/src/routes/v3/project_creation.rs +++ b/apps/labrinth/src/routes/v3/project_creation.rs @@ -1,6 +1,6 @@ use super::version_creation::{InitialVersionData, try_create_version_fields}; use crate::auth::{ - AuthenticationError, StandingRequirement, get_user_from_headers, + AccountLockRequirement, AuthenticationError, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; @@ -483,7 +483,7 @@ async fn project_create_inner( redis, session_queue, Scopes::PROJECT_CREATE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await?; diff --git a/apps/labrinth/src/routes/v3/project_creation/new.rs b/apps/labrinth/src/routes/v3/project_creation/new.rs index 1d4b877f7f..c3697c0705 100644 --- a/apps/labrinth/src/routes/v3/project_creation/new.rs +++ b/apps/labrinth/src/routes/v3/project_creation/new.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use actix_http::StatusCode; use actix_web::{HttpRequest, HttpResponse, ResponseError, put, web}; use eyre::eyre; @@ -137,7 +137,7 @@ pub async fn create( &redis, &session_queue, Scopes::PROJECT_CREATE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating project creator")?; diff --git a/apps/labrinth/src/routes/v3/projects/mod.rs b/apps/labrinth/src/routes/v3/projects/mod.rs index 7d5f5dcc36..cb8e1f0c68 100644 --- a/apps/labrinth/src/routes/v3/projects/mod.rs +++ b/apps/labrinth/src/routes/v3/projects/mod.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use crate::auth::checks::{filter_visible_versions, is_visible_project}; use crate::auth::{ - StandingRequirement, filter_visible_projects, get_user_from_headers, + AccountLockRequirement, filter_visible_projects, get_user_from_headers, }; use crate::database::models::notification_item::NotificationBuilder; use crate::database::models::project_item::{ @@ -267,7 +267,7 @@ pub async fn projects_get( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -315,7 +315,7 @@ pub async fn project_get_internal( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|(_, user)| user) @@ -461,7 +461,7 @@ pub async fn project_edit_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1755,7 +1755,7 @@ pub async fn dependency_list_internal( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -1914,7 +1914,7 @@ pub async fn projects_edit( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2292,7 +2292,7 @@ pub async fn project_icon_edit_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2446,7 +2446,7 @@ pub async fn delete_project_icon_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2610,7 +2610,7 @@ pub async fn add_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2842,7 +2842,7 @@ pub async fn edit_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3074,7 +3074,7 @@ pub async fn delete_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3226,7 +3226,7 @@ pub async fn project_delete_internal( &redis, &session_queue, Scopes::PROJECT_DELETE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -3537,7 +3537,7 @@ pub async fn project_follow_internal( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3643,7 +3643,7 @@ pub async fn project_unfollow_internal( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3731,7 +3731,7 @@ pub async fn project_get_organization( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::ORGANIZATION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/v3/projects/validate.rs b/apps/labrinth/src/routes/v3/projects/validate.rs index 877242e7b2..7b62ae0c47 100644 --- a/apps/labrinth/src/routes/v3/projects/validate.rs +++ b/apps/labrinth/src/routes/v3/projects/validate.rs @@ -3,7 +3,7 @@ use eyre::eyre; use serde::Serialize; use xredis::RedisPool; -use crate::auth::get_user_from_headers; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::models::DBProjectId; use crate::database::models::project_item::ProjectQueryResult; use crate::database::{ @@ -102,6 +102,7 @@ pub async fn validate( &redis, &session_queue, Scopes::PROJECT_READ, + AccountLockRequirement::None, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/reports.rs b/apps/labrinth/src/routes/v3/reports.rs index 021146225b..13bc236991 100644 --- a/apps/labrinth/src/routes/v3/reports.rs +++ b/apps/labrinth/src/routes/v3/reports.rs @@ -1,5 +1,6 @@ use crate::auth::{ - StandingRequirement, check_is_moderator_from_headers, get_user_from_headers, + AccountLockRequirement, check_is_moderator_from_headers, + get_user_from_headers, }; use crate::database; use crate::database::PgPool; @@ -87,7 +88,7 @@ pub async fn report_create( &redis, &session_queue, Scopes::REPORT_CREATE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -413,7 +414,7 @@ pub async fn reports( &redis, &session_queue, Scopes::REPORT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -524,7 +525,7 @@ pub async fn reports_get( &redis, &session_queue, Scopes::REPORT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -564,7 +565,7 @@ pub async fn report_get( &redis, &session_queue, Scopes::REPORT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -622,7 +623,7 @@ pub async fn report_edit( &redis, &session_queue, Scopes::REPORT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -760,7 +761,7 @@ pub async fn report_delete( &redis, &session_queue, Scopes::REPORT_DELETE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/teams.rs b/apps/labrinth/src/routes/v3/teams.rs index 9ae716d842..25255b67f0 100644 --- a/apps/labrinth/src/routes/v3/teams.rs +++ b/apps/labrinth/src/routes/v3/teams.rs @@ -1,5 +1,5 @@ use crate::auth::checks::{is_visible_organization, is_visible_project}; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::DBProject; use crate::database::PgPool; use crate::database::models::notification_item::NotificationBuilder; @@ -72,7 +72,7 @@ pub async fn team_members_get_project_internal( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -162,7 +162,7 @@ pub async fn team_members_get_organization( &redis, &session_queue, Scopes::ORGANIZATION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -263,7 +263,7 @@ pub async fn team_members_get( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -355,7 +355,7 @@ pub async fn teams_get( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -422,7 +422,7 @@ pub async fn join_team( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -541,7 +541,7 @@ pub async fn add_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -828,7 +828,7 @@ pub async fn edit_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1042,7 +1042,7 @@ pub async fn transfer_ownership( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1258,7 +1258,7 @@ pub async fn remove_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/threads.rs b/apps/labrinth/src/routes/v3/threads.rs index c4e1037d1a..b0325087e0 100644 --- a/apps/labrinth/src/routes/v3/threads.rs +++ b/apps/labrinth/src/routes/v3/threads.rs @@ -1,4 +1,4 @@ -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::database::models::image_item; @@ -297,7 +297,7 @@ pub async fn thread_get( &redis, &session_queue, Scopes::THREAD_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -371,7 +371,7 @@ pub async fn threads_get( &redis, &session_queue, Scopes::THREAD_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -429,7 +429,7 @@ pub async fn thread_send_message( &redis, &session_queue, Scopes::THREAD_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -700,7 +700,7 @@ pub async fn message_delete( &redis, &session_queue, Scopes::THREAD_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/users.rs b/apps/labrinth/src/routes/v3/users.rs index 2018c7f2e7..22356bf083 100644 --- a/apps/labrinth/src/routes/v3/users.rs +++ b/apps/labrinth/src/routes/v3/users.rs @@ -1,4 +1,4 @@ -use crate::auth::StandingRequirement; +use crate::auth::AccountLockRequirement; use crate::auth::validate::get_full_user_from_headers; use crate::util::error::ApiContext as _; use std::{ @@ -26,7 +26,7 @@ use crate::{ organizations::Organization, pats::Scopes, projects::Project, - users::{AccountStanding, Badges, Role, User}, + users::{Badges, Role, User}, }, queue::session::AuthQueue, util::{img::delete_old_images, routes::read_limited_from_payload}, @@ -88,7 +88,7 @@ pub async fn all_projects( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -237,7 +237,7 @@ pub async fn admin_user_email( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -304,7 +304,7 @@ pub async fn projects_list( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -358,16 +358,16 @@ pub async fn user_auth_get( &redis, &session_queue, Scopes::USER_READ, - StandingRequirement::None, + AccountLockRequirement::None, ) .await .wrap_auth_err("authenticating API request")?; - let mut user = match db_user.account_standing { - AccountStanding::Full => User::from_full(db_user), - AccountStanding::Locked => { + let mut user = match db_user.account_locked { + false => User::from_full(db_user), + true => { let mut user = User::from(db_user); - user.account_standing = Some(AccountStanding::Locked); + user.account_locked = Some(true); return Ok(HttpResponse::Ok().json(user)); } }; @@ -405,7 +405,7 @@ pub async fn get_user_preferences( &redis, &session_queue, Scopes::USER_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -450,7 +450,7 @@ pub async fn edit_user_preferences( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -561,18 +561,18 @@ pub async fn users_get( &redis, &session_queue, Scopes::empty(), - StandingRequirement::None, + AccountLockRequirement::None, ) .await .ok(); let is_mod = auth_user.as_ref().is_some_and(|(scopes, user)| { - match user.account_standing { - AccountStanding::Full => { + match user.account_locked { + false => { scopes.contains(Scopes::SESSION_ACCESS) && Role::from_string(&user.role).is_mod() } - AccountStanding::Locked => false, + true => false, } }); let notes = if is_mod { @@ -591,15 +591,15 @@ pub async fn users_get( .into_iter() .map(|data| { let user_id = data.id; - let visible_standing = auth_user + let visible_account_locked = auth_user .as_ref() .filter(|(_, viewer)| { viewer.id == user_id || Role::from_string(&viewer.role).is_admin() }) - .map(|_| data.account_standing); + .map(|_| data.account_locked); let mut user = User::from(data); - user.account_standing = visible_standing; + user.account_locked = visible_account_locked; if is_mod { user.moderation_notes = Some(notes.get(&user_id).cloned().map(Into::into)); @@ -641,30 +641,30 @@ pub async fn user_get( &redis, &session_queue, Scopes::empty(), - StandingRequirement::None, + AccountLockRequirement::None, ) .await .ok(); let staff_role = auth_user.as_ref().and_then(|(scopes, user)| { - match user.account_standing { - AccountStanding::Full => scopes + match user.account_locked { + false => scopes .contains(Scopes::SESSION_ACCESS) .then(|| Role::from_string(&user.role)), - AccountStanding::Locked => None, + true => None, } }); let is_admin = staff_role.as_ref().is_some_and(Role::is_admin); let is_mod = staff_role.as_ref().is_some_and(Role::is_mod); let user_id = data.id; - let visible_standing = auth_user + let visible_account_locked = auth_user .as_ref() .filter(|(_, viewer)| { viewer.id == user_id || Role::from_string(&viewer.role).is_admin() }) - .map(|_| data.account_standing); + .map(|_| data.account_locked); let mut response = if is_admin { let github_id = @@ -680,7 +680,7 @@ pub async fn user_get( data.into() }; - response.account_standing = visible_standing; + response.account_locked = visible_account_locked; if is_mod { let note = DBModerationNote::get_user(user_id, &**pool, &redis) @@ -711,7 +711,7 @@ pub async fn user_notes_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -797,7 +797,7 @@ pub async fn collections_list( &redis, &session_queue, Scopes::COLLECTION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -845,7 +845,7 @@ pub async fn orgs_list( &redis, &session_queue, Scopes::PROJECT_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -949,7 +949,7 @@ pub struct EditUser { #[validate(length(max = 160))] pub bio: Option>, pub role: Option, - pub account_standing: Option, + pub account_locked: Option, pub badges: Option, #[validate(length(max = 160))] pub venmo_handle: Option, @@ -983,7 +983,7 @@ pub async fn user_edit( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1051,23 +1051,23 @@ pub async fn user_edit( .wrap_internal_err("fetching bio from database")?; } - if let Some(account_standing) = new_user.account_standing { + if let Some(account_locked) = new_user.account_locked { if !user.role.is_admin() { return Err(ApiError::Auth(eyre::eyre!( - "only admins can edit account standing" + "only admins can edit account lock" ))); } sqlx::query!( r#" - UPDATE users SET account_standing = $1 WHERE id = $2 + UPDATE users SET account_locked = $1 WHERE id = $2 "#, - account_standing.as_str(), + account_locked, id as DBUserId, ) .execute(&mut transaction) .await - .wrap_internal_err("updating account standing")?; + .wrap_internal_err("updating account lock")?; } if let Some(role) = &new_user.role { @@ -1222,7 +1222,7 @@ pub async fn user_icon_edit( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1318,7 +1318,7 @@ pub async fn user_icon_delete( &redis, &session_queue, Scopes::USER_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1390,7 +1390,7 @@ pub async fn user_delete( &redis, &session_queue, Scopes::USER_DELETE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1454,7 +1454,7 @@ pub async fn user_follows( &redis, &session_queue, Scopes::USER_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1515,7 +1515,7 @@ pub async fn user_notifications( &redis, &session_queue, Scopes::NOTIFICATION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/version_creation.rs b/apps/labrinth/src/routes/v3/version_creation.rs index dcfd7fe253..672d9ac790 100644 --- a/apps/labrinth/src/routes/v3/version_creation.rs +++ b/apps/labrinth/src/routes/v3/version_creation.rs @@ -1,5 +1,5 @@ use super::project_creation::{CreateError, UploadedFile}; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::PgTransaction; use crate::database::models::loader_fields::{ @@ -223,7 +223,7 @@ async fn version_create_inner( redis, session_queue, Scopes::VERSION_CREATE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .1; @@ -706,7 +706,7 @@ async fn upload_file_to_version_inner( &redis, session_queue, Scopes::VERSION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await? .1; diff --git a/apps/labrinth/src/routes/v3/version_file.rs b/apps/labrinth/src/routes/v3/version_file.rs index f3cda142d3..7681c0d8f8 100644 --- a/apps/labrinth/src/routes/v3/version_file.rs +++ b/apps/labrinth/src/routes/v3/version_file.rs @@ -1,7 +1,7 @@ use super::ApiError; use crate::auth::checks::{filter_visible_versions, is_visible_version}; use crate::auth::{ - StandingRequirement, filter_visible_projects, get_user_from_headers, + AccountLockRequirement, filter_visible_projects, get_user_from_headers, }; use crate::database::PgPool; use crate::database::ReadOnlyPgPool; @@ -80,7 +80,7 @@ pub async fn get_version_from_hash( &redis, &session_queue, Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -213,7 +213,7 @@ pub async fn get_update_from_hash( &redis, &session_queue, Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -337,7 +337,7 @@ pub async fn get_versions_from_hashes( &redis, &session_queue, Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -421,7 +421,7 @@ pub async fn get_projects_from_hashes( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -703,7 +703,7 @@ pub async fn update_individual_files( &redis, &session_queue, Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -864,7 +864,7 @@ pub async fn delete_file( &redis, &session_queue, Scopes::VERSION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1049,7 +1049,7 @@ pub async fn download_version( &redis, &session_queue, Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) diff --git a/apps/labrinth/src/routes/v3/versions.rs b/apps/labrinth/src/routes/v3/versions.rs index 5fdd8e7a3e..9ac5604ea5 100644 --- a/apps/labrinth/src/routes/v3/versions.rs +++ b/apps/labrinth/src/routes/v3/versions.rs @@ -5,7 +5,7 @@ use super::ApiError; use crate::auth::checks::{ filter_visible_versions, is_visible_project, is_visible_version, }; -use crate::auth::{StandingRequirement, get_user_from_headers}; +use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database; use crate::database::models::loader_fields::{ self, LoaderField, LoaderFieldEnumValue, VersionField, @@ -90,7 +90,7 @@ pub async fn version_project_get_helper( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -218,7 +218,7 @@ pub async fn versions_get( &redis, &session_queue, Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -301,7 +301,7 @@ pub async fn version_get_helper( &redis, &session_queue, Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -473,7 +473,7 @@ pub async fn version_edit_helper( &redis, &session_queue, Scopes::VERSION_WRITE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1037,7 +1037,7 @@ pub async fn version_list_internal( &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -1236,7 +1236,7 @@ pub async fn version_delete( &redis, &session_queue, Scopes::VERSION_DELETE, - StandingRequirement::Full, + AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/tests/account_standing.rs b/apps/labrinth/tests/account_locked.rs similarity index 81% rename from apps/labrinth/tests/account_standing.rs rename to apps/labrinth/tests/account_locked.rs index 694e67a1af..0bd11d4ef2 100644 --- a/apps/labrinth/tests/account_standing.rs +++ b/apps/labrinth/tests/account_locked.rs @@ -15,29 +15,31 @@ use common::{ environment::{TestEnvironment, with_test_environment}, }; use labrinth::{ - auth::{AuthenticationError, StandingRequirement, get_user_from_headers}, + auth::{ + AccountLockRequirement, AuthenticationError, get_user_from_headers, + }, database::models::{ DBUserId, flow_item::DBFlow, session_item::SessionBuilder, }, env::ENV, - models::{pats::Scopes, users::AccountStanding}, + models::pats::Scopes, queue::session::AuthQueue, }; use serde_json::{Value, json}; pub mod common; -async fn set_standing( +async fn set_account_locked( env: &TestEnvironment, user: &str, - standing: AccountStanding, + account_locked: bool, ) { let response = env .call( test::TestRequest::patch() .uri(&format!("/v3/user/{user}")) .append_pat(ADMIN_USER_PAT) - .set_json(json!({"account_standing": standing})) + .set_json(json!({"account_locked": account_locked})) .to_request(), ) .await; @@ -45,7 +47,8 @@ async fn set_standing( } #[actix_rt::test] -async fn standing_is_visible_only_to_self_and_admin_in_both_api_versions() { +async fn account_locked_is_visible_only_to_self_and_admin_in_both_api_versions() +{ with_test_environment(None, |env: TestEnvironment| async move { let mut restricted_tokens = Vec::new(); for pat in [USER_USER_PAT, ADMIN_USER_PAT] { @@ -55,7 +58,7 @@ async fn standing_is_visible_only_to_self_and_admin_in_both_api_versions() { .uri("/_internal/pat") .append_pat(pat) .set_json(json!({ - "name": "standing visibility", + "name": "account lock visibility", "scopes": Scopes::empty(), "expires": Utc::now() + Duration::days(1), })) @@ -67,8 +70,8 @@ async fn standing_is_visible_only_to_self_and_admin_in_both_api_versions() { restricted_tokens .push(body["access_token"].as_str().unwrap().to_owned()); } - for standing in [AccountStanding::Full, AccountStanding::Locked] { - set_standing(&env, "3", standing).await; + for account_locked in [false, true] { + set_account_locked(&env, "3", account_locked).await; for version in ["v2", "v3"] { for (pat, visible) in [ (None, false), @@ -91,8 +94,8 @@ async fn standing_is_visible_only_to_self_and_admin_in_both_api_versions() { assert_status!(&response, StatusCode::OK); let body: Value = test::read_body_json(response).await; assert_eq!( - body.get("account_standing"), - visible.then(|| json!(standing)).as_ref() + body.get("account_locked"), + visible.then(|| json!(account_locked)).as_ref() ); assert_eq!( body["email"].is_string(), @@ -132,14 +135,14 @@ async fn standing_is_visible_only_to_self_and_admin_in_both_api_versions() { assert_eq!(body.len(), 2); for user in body { let expected = if user["id"] == "3" { - visible.then(|| json!(standing)) + visible.then(|| json!(account_locked)) } else { (pat == ADMIN_USER_PAT || pat == Some(restricted_tokens[1].as_str())) - .then(|| json!("full")) + .then(|| json!(false)) }; assert_eq!( - user.get("account_standing"), + user.get("account_locked"), expected.as_ref() ); } @@ -154,10 +157,10 @@ async fn standing_is_visible_only_to_self_and_admin_in_both_api_versions() { .await; assert_status!(&response, StatusCode::OK); let body: Value = test::read_body_json(response).await; - assert_eq!(body["account_standing"], json!(standing)); - match standing { - AccountStanding::Full => assert!(body["email"].is_string()), - AccountStanding::Locked => { + assert_eq!(body["account_locked"], json!(account_locked)); + match account_locked { + false => assert!(body["email"].is_string()), + true => { for field in [ "email", "email_verified", @@ -183,7 +186,7 @@ async fn standing_is_visible_only_to_self_and_admin_in_both_api_versions() { } #[actix_rt::test] -async fn existing_pat_session_and_oauth_credentials_obey_standing_and_cache_invalidation() +async fn existing_pat_session_and_oauth_credentials_obey_account_locks_and_cache_invalidation() { with_test_environment(None, |env: TestEnvironment| async move { let client = &env.dummy.oauth_client_alpha; @@ -200,14 +203,14 @@ async fn existing_pat_session_and_oauth_credentials_obey_standing_and_cache_inva .await; let mut transaction = env.db.pool.begin().await.unwrap(); SessionBuilder { - session: "mra_standing_test".into(), + session: "mra_account_locked_test".into(), user_id: DBUserId(3), os: None, platform: None, city: None, country: None, ip: "127.0.0.1".into(), - user_agent: "standing test".into(), + user_agent: "account lock test".into(), expires: None, session_expires: None, } @@ -217,16 +220,12 @@ async fn existing_pat_session_and_oauth_credentials_obey_standing_and_cache_inva transaction.commit().await.unwrap(); let tokens = [ USER_USER_PAT.unwrap(), - "mra_standing_test", + "mra_account_locked_test", oauth_token.as_str(), ]; let queue = AuthQueue::new(); - for standing in [ - AccountStanding::Full, - AccountStanding::Locked, - AccountStanding::Full, - ] { - set_standing(&env, "3", standing).await; + for account_locked in [false, true, false] { + set_account_locked(&env, "3", account_locked).await; for token in tokens { let req = test::TestRequest::get() .append_pat(Some(token)) @@ -235,9 +234,10 @@ async fn existing_pat_session_and_oauth_credentials_obey_standing_and_cache_inva ENV.RATE_LIMIT_IGNORE_KEY.as_str(), )) .to_http_request(); - for requirement in - [StandingRequirement::None, StandingRequirement::Full] - { + for requirement in [ + AccountLockRequirement::None, + AccountLockRequirement::NotLocked, + ] { let result = get_user_from_headers( &req, &*env.db.pool, @@ -247,24 +247,18 @@ async fn existing_pat_session_and_oauth_credentials_obey_standing_and_cache_inva requirement, ) .await; - match (standing, requirement) { - ( - AccountStanding::Locked, - StandingRequirement::Full, - ) => { + match (account_locked, requirement) { + (true, AccountLockRequirement::NotLocked) => { assert!(matches!( result, Err(AuthenticationError::AccountLocked) )); } - (AccountStanding::Full, StandingRequirement::Full) - | ( - AccountStanding::Full | AccountStanding::Locked, - StandingRequirement::None, - ) => { + (false, AccountLockRequirement::NotLocked) + | (_, AccountLockRequirement::None) => { assert_eq!( - result.unwrap().1.account_standing, - Some(standing) + result.unwrap().1.account_locked, + Some(account_locked) ); } } @@ -280,7 +274,7 @@ async fn existing_pat_session_and_oauth_credentials_obey_standing_and_cache_inva &env.db.redis_pool, &queue, Scopes::USER_AUTH_WRITE, - StandingRequirement::None, + AccountLockRequirement::None, ) .await; assert!(matches!( @@ -297,13 +291,13 @@ async fn locked_accounts_cannot_mutate_resources_read_sensitive_data_or_unlock_t with_test_environment(None, |env: TestEnvironment| async move { for pat in [USER_USER_PAT, MOD_USER_PAT] { let response = env.call(test::TestRequest::patch().uri("/v3/user/3") - .append_pat(pat).set_json(json!({"account_standing": "locked"})).to_request()).await; + .append_pat(pat).set_json(json!({"account_locked": true})).to_request()).await; assert_status!(&response, StatusCode::UNAUTHORIZED); } - set_standing(&env, "3", AccountStanding::Locked).await; + set_account_locked(&env, "3", true).await; let project = format!("/v3/project/{}", env.dummy.project_alpha.project_id); for (method, uri, body) in [ - (Method::PATCH, "/v3/user/3", json!({"account_standing":"full"})), + (Method::PATCH, "/v3/user/3", json!({"account_locked":false})), (Method::PATCH, project.as_str(), json!({"title":"changed"})), (Method::PATCH, "/v3/organization/missing", json!({"name":"changed"})), (Method::PATCH, "/v3/collection/missing", json!({"name":"changed"})), @@ -318,9 +312,9 @@ async fn locked_accounts_cannot_mutate_resources_read_sensitive_data_or_unlock_t let body: Value = test::read_body_json(response).await; assert_eq!(body["error"], "auth_error", "{uri}"); } - set_standing(&env, "1", AccountStanding::Locked).await; + set_account_locked(&env, "1", true).await; let response = env.call(test::TestRequest::patch().uri("/v3/user/3") - .append_pat(ADMIN_USER_PAT).set_json(json!({"account_standing":"full"})).to_request()).await; + .append_pat(ADMIN_USER_PAT).set_json(json!({"account_locked":false})).to_request()).await; assert_status!(&response, StatusCode::FORBIDDEN); }).await; } @@ -337,7 +331,7 @@ async fn flows_created_before_lock_cannot_reset_password_verify_email_or_issue_t let flow = get_authorize_accept_flow_id(response).await; let response = env.api.oauth_accept(&flow, USER_USER_PAT).await; let auth_code = get_auth_code_from_redirect_params(&response).await; - set_standing(&env, "3", AccountStanding::Locked).await; + set_account_locked(&env, "3", true).await; for (method, uri, body) in [ (Method::PATCH, "/_internal/auth/password", json!({"flow":password_flow,"new_password":"a long secure test password 489313"})), From f11f61f7503bd2e8b0a2a448e8aeb9d0f74c7d78 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-X=2E=20T=2E?= Date: Fri, 4 Sep 2026 23:59:05 -0400 Subject: [PATCH 3/4] actually make things simpler --- apps/labrinth/src/auth/mod.rs | 2 +- apps/labrinth/src/auth/oauth/mod.rs | 4 +- apps/labrinth/src/auth/validate.rs | 118 +++++++++------ .../src/database/models/passkey_item.rs | 28 ++-- apps/labrinth/src/routes/analytics.rs | 16 +- apps/labrinth/src/routes/internal/admin.rs | 15 +- .../labrinth/src/routes/internal/affiliate.rs | 14 +- .../src/routes/internal/analytics_event.rs | 4 - .../src/routes/internal/attribution.rs | 13 +- apps/labrinth/src/routes/internal/billing.rs | 14 +- .../src/routes/internal/delphi/mod.rs | 4 - .../routes/internal/external_notifications.rs | 3 +- apps/labrinth/src/routes/internal/flows.rs | 41 ++--- apps/labrinth/src/routes/internal/gdpr.rs | 3 +- apps/labrinth/src/routes/internal/medal.rs | 8 +- .../internal/moderation/external_license.rs | 7 - .../src/routes/internal/moderation/mod.rs | 16 +- .../routes/internal/moderation/tech_review.rs | 13 -- .../internal/moderation/tech_review/global.rs | 3 - .../internal/moderation/tech_review/rules.rs | 7 - .../moderation/tech_review/rules_scan.rs | 4 - apps/labrinth/src/routes/internal/pats.rs | 6 +- apps/labrinth/src/routes/internal/privacy.rs | 5 +- .../src/routes/internal/server_ping.rs | 2 - apps/labrinth/src/routes/internal/session.rs | 7 +- apps/labrinth/src/routes/internal/statuses.rs | 2 - apps/labrinth/src/routes/maven.rs | 32 ++-- apps/labrinth/src/routes/updates.rs | 9 +- .../src/routes/v3/analytics_get/facets/mod.rs | 2 - .../src/routes/v3/analytics_get/mod.rs | 2 - apps/labrinth/src/routes/v3/blocked_users.rs | 5 +- apps/labrinth/src/routes/v3/collections.rs | 23 +-- apps/labrinth/src/routes/v3/content/mod.rs | 10 +- apps/labrinth/src/routes/v3/disclosures.rs | 11 +- apps/labrinth/src/routes/v3/friends.rs | 5 +- apps/labrinth/src/routes/v3/images.rs | 3 +- apps/labrinth/src/routes/v3/limits.rs | 4 - apps/labrinth/src/routes/v3/notifications.rs | 8 +- apps/labrinth/src/routes/v3/oauth_clients.rs | 9 -- apps/labrinth/src/routes/v3/organizations.rs | 33 ++-- apps/labrinth/src/routes/v3/payouts.rs | 10 +- .../src/routes/v3/project_creation.rs | 4 +- .../src/routes/v3/project_creation/new.rs | 2 - apps/labrinth/src/routes/v3/projects/mod.rs | 43 ++---- .../src/routes/v3/projects/validate.rs | 3 +- apps/labrinth/src/routes/v3/reports.rs | 11 +- apps/labrinth/src/routes/v3/teams.rs | 36 ++--- apps/labrinth/src/routes/v3/threads.rs | 6 +- apps/labrinth/src/routes/v3/users.rs | 89 ++++------- .../src/routes/v3/version_creation.rs | 4 +- apps/labrinth/src/routes/v3/version_file.rs | 48 +++--- apps/labrinth/src/routes/v3/versions.rs | 33 ++-- apps/labrinth/tests/account_locked.rs | 141 ++++++++++++++---- 53 files changed, 391 insertions(+), 554 deletions(-) diff --git a/apps/labrinth/src/auth/mod.rs b/apps/labrinth/src/auth/mod.rs index 0a4edcf591..def5440a80 100644 --- a/apps/labrinth/src/auth/mod.rs +++ b/apps/labrinth/src/auth/mod.rs @@ -9,7 +9,7 @@ pub use checks::{ }; use serde::{Deserialize, Serialize}; pub use validate::{ - AccountLockRequirement, check_is_moderator_from_headers, + check_account_unlocked, check_is_moderator_from_headers, get_user_from_bearer_token, get_user_from_headers, }; diff --git a/apps/labrinth/src/auth/oauth/mod.rs b/apps/labrinth/src/auth/oauth/mod.rs index 8ff7ad7a5f..9d02719e90 100644 --- a/apps/labrinth/src/auth/oauth/mod.rs +++ b/apps/labrinth/src/auth/oauth/mod.rs @@ -1,8 +1,8 @@ use std::fmt::Write; +use crate::auth::get_user_from_headers; use crate::auth::oauth::uris::{OAuthRedirectUris, ValidatedRedirectUri}; use crate::auth::validate::extract_authorization_header; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::flow_item::DBFlow; use crate::database::models::oauth_client_authorization_item::DBOAuthClientAuthorization; @@ -83,7 +83,6 @@ pub async fn init_oauth( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await? .1; @@ -354,7 +353,6 @@ pub async fn accept_or_reject_client_scopes( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await? .1; diff --git a/apps/labrinth/src/auth/validate.rs b/apps/labrinth/src/auth/validate.rs index 03880fc7b4..fe32c2531c 100644 --- a/apps/labrinth/src/auth/validate.rs +++ b/apps/labrinth/src/auth/validate.rs @@ -11,32 +11,22 @@ use actix_web::http::header::{AUTHORIZATION, HeaderValue}; use chrono::Utc; use xredis::RedisPool; -/// Account lock requirement, independent of token scopes and user role. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum AccountLockRequirement { - None, - NotLocked, -} - -impl AccountLockRequirement { - pub fn check( - self, - account_locked: bool, - ) -> Result<(), AuthenticationError> { - match (self, account_locked) { - (Self::NotLocked, true) => Err(AuthenticationError::AccountLocked), - (Self::NotLocked, false) | (Self::None, _) => Ok(()), - } +pub fn check_account_unlocked( + account_locked: bool, +) -> Result<(), AuthenticationError> { + match account_locked { + true => Err(AuthenticationError::AccountLocked), + false => Ok(()), } } +/// Allows anonymous or invalid credentials, but rejects locked accounts. pub async fn get_maybe_user_from_headers<'a, E>( req: &HttpRequest, executor: E, redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, - account_lock_requirement: AccountLockRequirement, ) -> Result, AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -45,26 +35,21 @@ where return Ok(None); } - // Fetch DB user record and minos user from headers - let Some((scopes, db_user)) = get_user_record_from_bearer_token( + match get_user_from_headers( req, - None, executor, redis, session_queue, - false, - account_lock_requirement, + required_scopes, ) - .await? - else { - return Ok(None); - }; - - if !scopes.contains(required_scopes) { - return Ok(None); + .await + { + Ok(user) => Ok(Some(user)), + Err(AuthenticationError::AccountLocked) => { + Err(AuthenticationError::AccountLocked) + } + Err(_) => Ok(None), } - - Ok(Some((scopes, User::from_full(db_user)))) } pub async fn get_full_user_from_headers<'a, E>( @@ -73,7 +58,6 @@ pub async fn get_full_user_from_headers<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, - account_lock_requirement: AccountLockRequirement, ) -> Result<(Scopes, DBUser), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -85,7 +69,34 @@ where redis, session_queue, false, - account_lock_requirement, + ) + .await? + .ok_or(AuthenticationError::InvalidCredentials)?; + if !scopes.contains(required_scopes) { + return Err(AuthenticationError::InvalidCredentials); + } + Ok((scopes, db_user)) +} + +/// Authenticates without rejecting locked accounts. The caller must check +/// `account_locked` before exposing private data or allowing other actions. +pub async fn get_full_user_from_headers_allow_locked<'a, E>( + req: &HttpRequest, + executor: E, + redis: &RedisPool, + session_queue: &AuthQueue, + required_scopes: Scopes, +) -> Result<(Scopes, DBUser), AuthenticationError> +where + E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, +{ + let (scopes, db_user) = get_user_record_from_bearer_token_allow_locked( + req, + None, + executor, + redis, + session_queue, + false, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -104,7 +115,6 @@ pub async fn get_user_from_headers<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, - account_lock_requirement: AccountLockRequirement, ) -> Result<(Scopes, User), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -115,7 +125,6 @@ where redis, session_queue, required_scopes, - account_lock_requirement, ) .await?; @@ -129,7 +138,6 @@ pub async fn get_user_from_bearer_token<'a, E>( redis: &RedisPool, session_queue: &AuthQueue, allow_expired: bool, - account_lock_requirement: AccountLockRequirement, ) -> Result<(Scopes, User), AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -141,7 +149,6 @@ where redis, session_queue, allow_expired, - account_lock_requirement, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -149,15 +156,40 @@ where Ok((scopes, User::from_full(db_user))) } -pub async fn get_user_record_from_bearer_token<'a, 'b, E>( +pub async fn get_user_record_from_bearer_token<'a, E>( req: &HttpRequest, token: Option<&str>, executor: E, redis: &RedisPool, session_queue: &AuthQueue, allow_expired: bool, - account_lock_requirement: AccountLockRequirement, -) -> Result, AuthenticationError> +) -> Result, AuthenticationError> +where + E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, +{ + let user = get_user_record_from_bearer_token_allow_locked( + req, + token, + executor, + redis, + session_queue, + allow_expired, + ) + .await?; + if let Some((_, user)) = &user { + check_account_unlocked(user.account_locked)?; + } + Ok(user) +} + +async fn get_user_record_from_bearer_token_allow_locked<'a, E>( + req: &HttpRequest, + token: Option<&str>, + executor: E, + redis: &RedisPool, + session_queue: &AuthQueue, + allow_expired: bool, +) -> Result, AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, { @@ -257,10 +289,6 @@ where _ => return Err(AuthenticationError::InvalidAuthMethod), }; - if let Some((_, user)) = &possible_user { - account_lock_requirement.check(user.account_locked)?; - } - Ok(possible_user) } @@ -286,7 +314,6 @@ pub async fn check_is_moderator_from_headers<'a, 'b, E>( redis: &RedisPool, session_queue: &AuthQueue, required_scopes: Scopes, - account_lock_requirement: AccountLockRequirement, ) -> Result where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, @@ -297,7 +324,6 @@ where redis, session_queue, required_scopes, - account_lock_requirement, ) .await? .1; diff --git a/apps/labrinth/src/database/models/passkey_item.rs b/apps/labrinth/src/database/models/passkey_item.rs index ec5bc47354..bb0c1159e7 100644 --- a/apps/labrinth/src/database/models/passkey_item.rs +++ b/apps/labrinth/src/database/models/passkey_item.rs @@ -49,7 +49,7 @@ impl DBPasskey { pub async fn get_by_credential_id<'a, E>( credential_id: &[u8], exec: E, - ) -> Result, DatabaseError> + ) -> Result, DatabaseError> where E: crate::database::Executor<'a, Database = sqlx::Postgres>, { @@ -67,19 +67,19 @@ impl DBPasskey { ) .fetch_optional(exec) .await? - .map(|x| { - ( - DBPasskey { - id: DBPasskeyId(x.id), - user_id: DBUserId(x.user_id), - name: x.name, - credential_id: x.credential_id, - passkey: x.passkey.0, - created_at: x.created_at, - last_used: x.last_used, - }, - x.account_locked, - ) + .map(|x| { + ( + DBPasskey { + id: DBPasskeyId(x.id), + user_id: DBUserId(x.user_id), + name: x.name, + credential_id: x.credential_id, + passkey: x.passkey.0, + created_at: x.created_at, + last_used: x.last_used, + }, + x.account_locked, + ) }); Ok(row) diff --git a/apps/labrinth/src/routes/analytics.rs b/apps/labrinth/src/routes/analytics.rs index 88c720ff45..16a1b268a3 100644 --- a/apps/labrinth/src/routes/analytics.rs +++ b/apps/labrinth/src/routes/analytics.rs @@ -1,4 +1,5 @@ -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; +use crate::auth::validate::get_maybe_user_from_headers; use crate::database::PgPool; use crate::database::models::DBProject; use crate::env::ENV; @@ -74,16 +75,15 @@ pub async fn page_view_ingest( pool: web::Data, redis: web::Data, ) -> Result { - let user = get_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::None, ) .await - .ok(); + .wrap_auth_err("authenticating API request")?; let conn_info = req.connection_info().peer_addr().map(|x| x.to_string()); let url = Url::parse(&url_input.url) @@ -211,7 +211,6 @@ pub async fn playtime_ingest( &redis, &session_queue, Scopes::PERFORM_ANALYTICS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -292,17 +291,16 @@ pub async fn minecraft_server_play_ingest( redis: web::Data, http: web::Data, ) -> Result<(), ApiError> { - let user = get_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::None, ) .await - .map(|(_, user)| user) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|(_, user)| user); let project_id = play_input.project_id; diff --git a/apps/labrinth/src/routes/internal/admin.rs b/apps/labrinth/src/routes/internal/admin.rs index 1f52e4e020..c644d70d83 100644 --- a/apps/labrinth/src/routes/internal/admin.rs +++ b/apps/labrinth/src/routes/internal/admin.rs @@ -1,4 +1,4 @@ -use crate::auth::AccountLockRequirement; +use crate::auth::AuthenticationError; use crate::auth::validate::get_user_record_from_bearer_token; use crate::database::PgPool; use crate::models::analytics::{Download, DownloadReason}; @@ -162,18 +162,23 @@ pub async fn count_download( .find(|x| x.0.to_lowercase() == "authorization") .map(|x| &**x.1); - let user = get_user_record_from_bearer_token( + let user = match get_user_record_from_bearer_token( &req, token, &**pool, &redis, &session_queue, false, - AccountLockRequirement::None, ) .await - .ok() - .flatten(); + { + Ok(user) => user, + Err(AuthenticationError::AccountLocked) => { + return Err(AuthenticationError::AccountLocked) + .wrap_auth_err("authenticating API request"); + } + Err(_) => None, + }; let project_id: crate::database::models::ids::DBProjectId = download_body.project_id.into(); diff --git a/apps/labrinth/src/routes/internal/affiliate.rs b/apps/labrinth/src/routes/internal/affiliate.rs index 494121de5d..9ff53c99c7 100644 --- a/apps/labrinth/src/routes/internal/affiliate.rs +++ b/apps/labrinth/src/routes/internal/affiliate.rs @@ -1,4 +1,4 @@ -use crate::auth::AccountLockRequirement; +use crate::auth::validate::get_maybe_user_from_headers; use std::{collections::HashMap, net::Ipv4Addr, sync::Arc}; use xredis::RedisPool; @@ -54,17 +54,16 @@ pub async fn ingest_click( session_queue: web::Data, analytics_queue: web::Data>, ) -> Result<(), ApiError> { - let user = get_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::None, ) .await - .map(|(_, user)| user) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|(_, user)| user); let conn_info = req.connection_info().peer_addr().map(|x| x.to_string()); let url = ingest_click.url; @@ -160,7 +159,6 @@ pub async fn get_all( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -217,7 +215,6 @@ pub async fn create( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -303,7 +300,6 @@ pub async fn get( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -348,7 +344,6 @@ pub async fn delete( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -406,7 +401,6 @@ pub async fn patch( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/analytics_event.rs b/apps/labrinth/src/routes/internal/analytics_event.rs index 45daf4f962..c68fc7c0c2 100644 --- a/apps/labrinth/src/routes/internal/analytics_event.rs +++ b/apps/labrinth/src/routes/internal/analytics_event.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use actix_web::{HttpRequest, delete, patch, post, web}; use chrono::{DateTime, Utc}; use eyre::eyre; @@ -56,7 +55,6 @@ pub async fn analytics_event_create( &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -118,7 +116,6 @@ pub async fn analytics_event_edit( &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -170,7 +167,6 @@ pub async fn analytics_event_delete( &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/attribution.rs b/apps/labrinth/src/routes/internal/attribution.rs index ec1c6e7b06..444065345c 100644 --- a/apps/labrinth/src/routes/internal/attribution.rs +++ b/apps/labrinth/src/routes/internal/attribution.rs @@ -4,10 +4,7 @@ use chrono::{DateTime, Utc}; use eyre::eyre; use serde::{Deserialize, Serialize}; -use crate::auth::{ - AccountLockRequirement, check_is_moderator_from_headers, - get_user_from_headers, -}; +use crate::auth::{check_is_moderator_from_headers, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::{ DBFileId, DBOrganization, DBProject, DBTeamMember, DBVersion, @@ -128,7 +125,6 @@ pub async fn scan( &redis, &session_queue, Scopes::VERSION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -244,7 +240,6 @@ async fn force_scan_file( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -328,7 +323,6 @@ pub async fn list( &redis, &session_queue, Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -662,7 +656,6 @@ pub async fn update_group( &redis, &session_queue, Scopes::VERSION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -751,7 +744,6 @@ pub async fn delete_groups( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("deleting database records for `delete_groups`")?; @@ -791,7 +783,6 @@ pub async fn delete_all_groups( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("deleting database records for `delete_all_groups`")?; @@ -912,7 +903,6 @@ pub async fn assign( &redis, &session_queue, Scopes::VERSION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1076,7 +1066,6 @@ pub async fn split( &redis, &session_queue, Scopes::VERSION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/billing.rs b/apps/labrinth/src/routes/internal/billing.rs index 98d8bf04b7..5b4f971c96 100644 --- a/apps/labrinth/src/routes/internal/billing.rs +++ b/apps/labrinth/src/routes/internal/billing.rs @@ -1,6 +1,6 @@ use self::payments::*; use self::update_subscriptions::*; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database::models::charge_item::DBCharge; use crate::database::models::ids::DBUserSubscriptionId; use crate::database::models::notification_item::NotificationBuilder; @@ -139,7 +139,6 @@ pub async fn subscriptions( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -220,7 +219,6 @@ pub async fn refund_charge( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -512,7 +510,6 @@ pub async fn reprocess_charge_tax( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -714,7 +711,6 @@ pub async fn edit_subscription( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1213,7 +1209,6 @@ pub async fn user_customer( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1265,7 +1260,6 @@ pub async fn charges( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1332,7 +1326,6 @@ pub async fn add_payment_method_flow( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1395,7 +1388,6 @@ pub async fn edit_payment_method( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1470,7 +1462,6 @@ pub async fn remove_payment_method( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1572,7 +1563,6 @@ pub async fn payment_methods( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1750,7 +1740,6 @@ pub async fn initiate_payment( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2830,7 +2819,6 @@ pub async fn credit( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/delphi/mod.rs b/apps/labrinth/src/routes/internal/delphi/mod.rs index de28221741..87acfbef58 100644 --- a/apps/labrinth/src/routes/internal/delphi/mod.rs +++ b/apps/labrinth/src/routes/internal/delphi/mod.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use std::{collections::HashMap, fmt::Write, time::Instant}; use xredis::RedisPool; @@ -335,7 +334,6 @@ pub async fn _run( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -362,7 +360,6 @@ pub async fn version( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -395,7 +392,6 @@ pub async fn issue_type_schema( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("deserializing HTTP response")?; diff --git a/apps/labrinth/src/routes/internal/external_notifications.rs b/apps/labrinth/src/routes/internal/external_notifications.rs index 2c1f6ee859..e43eae6c54 100644 --- a/apps/labrinth/src/routes/internal/external_notifications.rs +++ b/apps/labrinth/src/routes/internal/external_notifications.rs @@ -2,7 +2,7 @@ use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::collections::HashMap; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database::PgPool; use crate::database::models::ids::{DBNotificationId, DBUserId}; use crate::database::models::notification_item::DBNotification; @@ -375,7 +375,6 @@ pub async fn send_custom_email( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/flows.rs b/apps/labrinth/src/routes/internal/flows.rs index 3697de2d27..1789db693a 100644 --- a/apps/labrinth/src/routes/internal/flows.rs +++ b/apps/labrinth/src/routes/internal/flows.rs @@ -2,7 +2,7 @@ use crate::auth::validate::{ get_full_user_from_headers, get_user_record_from_bearer_token, }; use crate::auth::{ - AccountLockRequirement, AuthProvider, AuthenticationError, + AuthProvider, AuthenticationError, check_account_unlocked, get_user_from_headers, }; use crate::database::PgPool; @@ -1108,7 +1108,6 @@ pub async fn init( &redis, &session_queue, false, - AccountLockRequirement::NotLocked, ) .await? .map(|(_scopes, user)| user.id) @@ -1148,7 +1147,6 @@ pub async fn init( &redis, &session_queue, false, - AccountLockRequirement::NotLocked, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -1337,7 +1335,7 @@ pub async fn auth_callback( let user = DBUser::get_id(id, &**client, &redis) .await? .ok_or(AuthenticationError::InvalidCredentials)?; - AccountLockRequirement::NotLocked.check(user.account_locked)?; + check_account_unlocked(user.account_locked)?; provider .update_user_id(id, Some(&oauth_user.id), &mut transaction) @@ -1368,7 +1366,7 @@ pub async fn auth_callback( .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; - AccountLockRequirement::NotLocked.check(user.account_locked)?; + check_account_unlocked(user.account_locked)?; if user.totp_secret.is_some() { let flow = DBFlow::Login2FA { user_id: user.id } @@ -1661,7 +1659,6 @@ pub async fn discord_community_link( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1734,7 +1731,6 @@ pub async fn delete_auth_provider( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2324,8 +2320,7 @@ pub async fn login_password( .map_err(|_| AuthenticationError::InvalidCredentials) .wrap_auth_err("authenticating API request")?; - AccountLockRequirement::NotLocked - .check(user.account_locked) + check_account_unlocked(user.account_locked) .wrap_auth_err("checking account lock")?; if user.totp_secret.is_some() { @@ -2469,8 +2464,7 @@ pub async fn login_2fa( .ok_or_else(|| AuthenticationError::InvalidCredentials) .wrap_auth_err("fetching user from database")?; - AccountLockRequirement::NotLocked - .check(user.account_locked) + check_account_unlocked(user.account_locked) .wrap_auth_err("checking account lock")?; let mut transaction = pool @@ -2542,7 +2536,6 @@ pub async fn begin_2fa_flow( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2604,7 +2597,6 @@ pub async fn finish_2fa_flow( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2753,7 +2745,6 @@ pub async fn remove_2fa( &redis, &session_queue, false, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2931,8 +2922,7 @@ pub async fn reset_password_begin( .. }) = user { - AccountLockRequirement::NotLocked - .check(account_locked) + check_account_unlocked(account_locked) .wrap_auth_err("checking account lock")?; let flow = DBFlow::ForgotPassword { user_id } @@ -3024,7 +3014,6 @@ pub async fn change_password( &redis, &session_queue, false, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3059,8 +3048,7 @@ pub async fn change_password( user }; - AccountLockRequirement::NotLocked - .check(user.account_locked) + check_account_unlocked(user.account_locked) .wrap_auth_err("checking account lock")?; let mut transaction = pool @@ -3210,7 +3198,6 @@ pub async fn set_email( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3341,7 +3328,6 @@ pub async fn resend_verify_email( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3436,8 +3422,7 @@ pub async fn verify_email( .ok_or_else(|| AuthenticationError::InvalidCredentials) .wrap_auth_err("fetching user from database")?; - AccountLockRequirement::NotLocked - .check(user.account_locked) + check_account_unlocked(user.account_locked) .wrap_auth_err("checking account lock")?; if user.email != Some(confirm_email) { @@ -3510,7 +3495,6 @@ pub async fn subscribe_newsletter( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3563,7 +3547,6 @@ pub async fn get_newsletter_subscription_status( &redis, &session_queue, Scopes::USER_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3618,7 +3601,6 @@ pub async fn register_passkey_start( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3722,7 +3704,6 @@ pub async fn register_passkey_finish( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3890,8 +3871,7 @@ pub async fn authenticate_passkey_finish( .wrap_internal_err("failed to fetch passkey")? .wrap_request_err_with(|| "passkey not found")?; - AccountLockRequirement::NotLocked - .check(account_locked) + check_account_unlocked(account_locked) .wrap_auth_err("checking account lock")?; let mut transaction = pool @@ -4009,7 +3989,6 @@ pub async fn list_passkeys( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -4064,7 +4043,6 @@ pub async fn rename_passkey( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -4125,7 +4103,6 @@ pub async fn delete_passkey( &redis, &session_queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/gdpr.rs b/apps/labrinth/src/routes/internal/gdpr.rs index 78ab9f6fcf..479209bd17 100644 --- a/apps/labrinth/src/routes/internal/gdpr.rs +++ b/apps/labrinth/src/routes/internal/gdpr.rs @@ -1,4 +1,4 @@ -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database::PgPool; use crate::models::pats::Scopes; use crate::queue::session::AuthQueue; @@ -30,7 +30,6 @@ pub async fn export( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/medal.rs b/apps/labrinth/src/routes/internal/medal.rs index 7a94f5ac16..a8f580546c 100644 --- a/apps/labrinth/src/routes/internal/medal.rs +++ b/apps/labrinth/src/routes/internal/medal.rs @@ -1,4 +1,4 @@ -use crate::auth::AccountLockRequirement; +use crate::auth::check_account_unlocked; use crate::database::PgPool; use crate::util::error::Context as _; use actix_web::{HttpResponse, post, web}; @@ -53,8 +53,7 @@ pub async fn verify( match maybe_fields { None => Err(ApiError::NotFound(eyre::eyre!("resource not found"))), Some(fields) => { - AccountLockRequirement::NotLocked - .check(fields.account_locked) + check_account_unlocked(fields.account_locked) .wrap_auth_err("checking account lock")?; Ok(HttpResponse::Ok().json(VerifyResponse { user_id: fields.user_id.into(), @@ -98,8 +97,7 @@ pub async fn redeem( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); } Some(fields) => { - AccountLockRequirement::NotLocked - .check(fields.account_locked) + check_account_unlocked(fields.account_locked) .wrap_auth_err("checking account lock")?; if fields.redeemal_status.is_some() { return Err(ApiError::Conflict(eyre::eyre!( diff --git a/apps/labrinth/src/routes/internal/moderation/external_license.rs b/apps/labrinth/src/routes/internal/moderation/external_license.rs index 1eb187521f..84ae825b04 100644 --- a/apps/labrinth/src/routes/internal/moderation/external_license.rs +++ b/apps/labrinth/src/routes/internal/moderation/external_license.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::collections::HashMap; @@ -358,7 +357,6 @@ pub async fn search( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating external license search")?; @@ -432,7 +430,6 @@ pub async fn lookup( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -473,7 +470,6 @@ pub async fn get_by_sha1( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -511,7 +507,6 @@ pub async fn get_by_sha1_bulk( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -572,7 +567,6 @@ async fn upsert_file_license( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -686,7 +680,6 @@ pub async fn update_license( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/mod.rs b/apps/labrinth/src/routes/internal/moderation/mod.rs index e41cab0868..d6f3871793 100644 --- a/apps/labrinth/src/routes/internal/moderation/mod.rs +++ b/apps/labrinth/src/routes/internal/moderation/mod.rs @@ -1,5 +1,5 @@ use super::ApiError; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database; use crate::database::PgPool; use crate::database::models::moderation_external_item; @@ -263,7 +263,6 @@ pub async fn get_projects_internal( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -748,7 +747,6 @@ pub async fn get_project_ids( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1098,7 +1096,6 @@ pub async fn get_project_meta( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1262,7 +1259,6 @@ pub async fn set_project_meta( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1369,7 +1365,6 @@ pub async fn acquire_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1434,7 +1429,6 @@ pub async fn override_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1486,7 +1480,6 @@ pub async fn get_lock_status( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1554,7 +1547,6 @@ pub async fn release_lock( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1620,7 +1612,6 @@ pub async fn release_lock_beacon( &redis, &session_queue, false, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1677,7 +1668,6 @@ pub async fn delete_all_locks( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1719,7 +1709,6 @@ pub async fn get_user_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1764,7 +1753,6 @@ pub async fn get_users_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1822,7 +1810,6 @@ pub async fn get_organization_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1871,7 +1858,6 @@ pub async fn get_organizations_project_by_status( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review.rs b/apps/labrinth/src/routes/internal/moderation/tech_review.rs index 88c5857a15..017a417944 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use std::{collections::HashMap, fmt}; use xredis::RedisPool; @@ -245,7 +244,6 @@ pub async fn get_issue( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -313,7 +311,6 @@ pub async fn get_report( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -764,7 +761,6 @@ pub async fn search_projects( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -997,7 +993,6 @@ pub async fn get_project_report( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1101,7 +1096,6 @@ pub async fn submit_report( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1309,7 +1303,6 @@ pub async fn update_issue_details( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("updating database records for `update_issue_details`")?; @@ -1463,7 +1456,6 @@ pub async fn update_global_issue_details( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err( @@ -1603,7 +1595,6 @@ pub async fn add_report( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("inserting database records for `add_report`")?; @@ -1688,7 +1679,6 @@ pub async fn get_user_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1729,7 +1719,6 @@ pub async fn get_users_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1785,7 +1774,6 @@ pub async fn get_organization_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1827,7 +1815,6 @@ pub async fn get_organizations_flagged_projects( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs index 355453f657..3ca452aea9 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/global.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use actix_web::{HttpRequest, post, web}; use itertools::Itertools; use serde::{Deserialize, Serialize}; @@ -148,7 +147,6 @@ pub async fn search_global_issue_details( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating global issue search")?; @@ -373,7 +371,6 @@ pub async fn get_global_issue_detail( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating global issue detail request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs index 29abdeeefa..0848065045 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/rules.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use actix_web::{HttpRequest, delete, get, post, put, web}; use chrono::{DateTime, Utc}; use eyre::eyre; @@ -194,7 +193,6 @@ pub async fn test_rule( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -251,7 +249,6 @@ pub async fn get_rules( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -429,7 +426,6 @@ pub async fn get_rule_affected_details( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -540,7 +536,6 @@ pub async fn create_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -629,7 +624,6 @@ pub async fn update_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -713,7 +707,6 @@ pub async fn delete_rule( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs b/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs index b88a59c0c3..facbe622ce 100644 --- a/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs +++ b/apps/labrinth/src/routes/internal/moderation/tech_review/rules_scan.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use std::collections::{BTreeMap, HashMap}; use actix_web::{HttpRequest, HttpResponse, get, post, web}; @@ -162,7 +161,6 @@ pub async fn get_rule_schema( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -208,7 +206,6 @@ pub async fn get_detail_rule_input( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -357,7 +354,6 @@ pub async fn scan_rules( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/pats.rs b/apps/labrinth/src/routes/internal/pats.rs index 63fcb18df1..63e4f10eba 100644 --- a/apps/labrinth/src/routes/internal/pats.rs +++ b/apps/labrinth/src/routes/internal/pats.rs @@ -2,7 +2,7 @@ use crate::database; use crate::database::models::generate_pat_id; use crate::util::error::Context as _; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::routes::ApiError; use actix_web::web::{self, Data}; @@ -53,7 +53,6 @@ pub async fn get_pats( &redis, &session_queue, Scopes::PAT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -130,7 +129,6 @@ pub async fn create_pat( &redis, &session_queue, Scopes::PAT_CREATE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -242,7 +240,6 @@ pub async fn edit_pat( &redis, &session_queue, Scopes::PAT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -361,7 +358,6 @@ pub async fn delete_pat( &redis, &session_queue, Scopes::PAT_DELETE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/privacy.rs b/apps/labrinth/src/routes/internal/privacy.rs index 301fadcc2f..16b6de0e7b 100644 --- a/apps/labrinth/src/routes/internal/privacy.rs +++ b/apps/labrinth/src/routes/internal/privacy.rs @@ -1,4 +1,4 @@ -use crate::auth::AccountLockRequirement; +use crate::auth::check_account_unlocked; use crate::database::PgPool; use crate::database::models::blocked_user_item::DBBlockedUser; use crate::database::models::friend_item::DBFriend; @@ -51,8 +51,7 @@ pub async fn invite_privacy_status( .wrap_internal_err("resolving user ids")?; let user = user.wrap_not_found_err("user not found")?; - AccountLockRequirement::NotLocked - .check(user.account_locked) + check_account_unlocked(user.account_locked) .wrap_auth_err("checking inviter account lock")?; let user_id = user.id; let target_id = target.wrap_not_found_err("target not found")?.id; diff --git a/apps/labrinth/src/routes/internal/server_ping.rs b/apps/labrinth/src/routes/internal/server_ping.rs index c21a908bc0..5847572ea5 100644 --- a/apps/labrinth/src/routes/internal/server_ping.rs +++ b/apps/labrinth/src/routes/internal/server_ping.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use std::time::Duration; use xredis::RedisPool; @@ -44,7 +43,6 @@ pub async fn ping_minecraft_java( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/internal/session.rs b/apps/labrinth/src/routes/internal/session.rs index 4dae6744e5..f7bd6f685c 100644 --- a/apps/labrinth/src/routes/internal/session.rs +++ b/apps/labrinth/src/routes/internal/session.rs @@ -1,7 +1,5 @@ use crate::auth::validate::get_user_from_bearer_token; -use crate::auth::{ - AccountLockRequirement, AuthenticationError, get_user_from_headers, -}; +use crate::auth::{AuthenticationError, get_user_from_headers}; use crate::database::models::DBUserId; use crate::database::models::session_item::DBSession; use crate::database::models::session_item::SessionBuilder; @@ -161,7 +159,6 @@ pub async fn list( &redis, &session_queue, Scopes::SESSION_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -218,7 +215,6 @@ pub async fn delete( &redis, &session_queue, Scopes::SESSION_DELETE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -296,7 +292,6 @@ pub async fn refresh( &redis, &session_queue, true, // Allow expired sessions, since we want to allow refreshing expired sessions - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/internal/statuses.rs b/apps/labrinth/src/routes/internal/statuses.rs index 88785fc290..03bd1a9e1c 100644 --- a/apps/labrinth/src/routes/internal/statuses.rs +++ b/apps/labrinth/src/routes/internal/statuses.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use crate::auth::AuthenticationError; use crate::auth::validate::get_user_record_from_bearer_token; use crate::database::models::friend_item::DBFriend; @@ -69,7 +68,6 @@ pub async fn ws_init( &redis, &session_queue, false, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/maven.rs b/apps/labrinth/src/routes/maven.rs index e7c7477022..a8f0ebff6b 100644 --- a/apps/labrinth/src/routes/maven.rs +++ b/apps/labrinth/src/routes/maven.rs @@ -1,5 +1,6 @@ -use crate::auth::AccountLockRequirement; use crate::auth::checks::{is_visible_project, is_visible_version}; +use crate::auth::validate::get_maybe_user_from_headers; +use crate::database; use crate::database::PgPool; use crate::database::models::legacy_loader_fields::MinecraftGameVersion; use crate::database::models::loader_fields::Loader; @@ -14,7 +15,6 @@ use crate::queue::session::AuthQueue; use crate::routes::ApiError; use crate::util::error::ApiContext as _; use crate::util::error::Context; -use crate::{auth::get_user_from_headers, database}; use actix_web::{HttpRequest, HttpResponse, get, route, web}; use quick_xml::escape::escape; use std::collections::HashSet; @@ -95,17 +95,16 @@ pub async fn maven_metadata( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); }; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await @@ -334,17 +333,16 @@ pub async fn version_file( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); }; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await @@ -422,17 +420,16 @@ pub async fn version_file_sha1( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); }; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await @@ -489,17 +486,16 @@ pub async fn version_file_sha512( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); }; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await diff --git a/apps/labrinth/src/routes/updates.rs b/apps/labrinth/src/routes/updates.rs index 09d0cdea77..41dc2db6ca 100644 --- a/apps/labrinth/src/routes/updates.rs +++ b/apps/labrinth/src/routes/updates.rs @@ -1,3 +1,4 @@ +use crate::auth::validate::get_maybe_user_from_headers; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::cmp::Reverse; @@ -9,7 +10,6 @@ use actix_web::{HttpRequest, HttpResponse, get, web}; use serde::{Deserialize, Serialize}; use crate::auth::checks::{filter_visible_versions, is_visible_project}; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; use crate::database; use crate::database::models::legacy_loader_fields::MinecraftGameVersion; use crate::models::pats::Scopes; @@ -61,17 +61,16 @@ pub async fn forge_updates( .wrap_api_err("fetching project from database")? .wrap_request_err_with(|| ERROR.to_string())?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await diff --git a/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs b/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs index 69de108c5f..4fd0c498e3 100644 --- a/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs +++ b/apps/labrinth/src/routes/v3/analytics_get/facets/mod.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use xredis::RedisPool; @@ -80,7 +79,6 @@ pub async fn fetch_facets( &redis, &session_queue, Scopes::ANALYTICS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/analytics_get/mod.rs b/apps/labrinth/src/routes/v3/analytics_get/mod.rs index 65aa7b40a6..71679c1e54 100644 --- a/apps/labrinth/src/routes/v3/analytics_get/mod.rs +++ b/apps/labrinth/src/routes/v3/analytics_get/mod.rs @@ -7,7 +7,6 @@ //! requests, you have to zip together M arrays of N elements //! - this makes it inconvenient to have separate endpoints -use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; @@ -196,7 +195,6 @@ pub async fn fetch_analytics( &redis, &session_queue, Scopes::ANALYTICS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/blocked_users.rs b/apps/labrinth/src/routes/v3/blocked_users.rs index 40d497e46b..2cdd00bbc9 100644 --- a/apps/labrinth/src/routes/v3/blocked_users.rs +++ b/apps/labrinth/src/routes/v3/blocked_users.rs @@ -1,4 +1,4 @@ -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database::PgPool; use crate::database::models::DBUser; use crate::database::models::blocked_user_item::DBBlockedUser; @@ -34,7 +34,6 @@ pub async fn block_user( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -93,7 +92,6 @@ pub async fn unblock_user( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -129,7 +127,6 @@ pub async fn get_blocked_users( &redis, &session_queue, Scopes::USER_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/collections.rs b/apps/labrinth/src/routes/v3/collections.rs index 96178e3622..a64dbdaefa 100644 --- a/apps/labrinth/src/routes/v3/collections.rs +++ b/apps/labrinth/src/routes/v3/collections.rs @@ -1,7 +1,7 @@ use crate::auth::checks::is_visible_collection; +use crate::auth::validate::get_maybe_user_from_headers; use crate::auth::{ - AccountLockRequirement, filter_visible_collections, get_user_from_headers, - require_verified_email, + filter_visible_collections, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; use crate::database::models::{ @@ -76,7 +76,6 @@ pub async fn collection_create( &redis, &session_queue, Scopes::COLLECTION_CREATE, - AccountLockRequirement::NotLocked, ) .await? .1; @@ -175,17 +174,16 @@ pub async fn collections_get( .await .wrap_internal_err("fetching collections from database")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::COLLECTION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let collections = filter_visible_collections(collections_data, &user_option, false) @@ -213,17 +211,16 @@ pub async fn collection_get( database::models::DBCollection::get(id, &**pool, &redis) .await .wrap_internal_err("fetching collection from database")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::COLLECTION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(data) = collection_data && is_visible_collection(&data, &user_option, false) @@ -271,7 +268,6 @@ pub async fn collection_edit( &redis, &session_queue, Scopes::COLLECTION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -455,7 +451,6 @@ pub async fn collection_icon_edit( &redis, &session_queue, Scopes::COLLECTION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -554,7 +549,6 @@ pub async fn delete_collection_icon( &redis, &session_queue, Scopes::COLLECTION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -626,7 +620,6 @@ pub async fn collection_delete( &redis, &session_queue, Scopes::COLLECTION_DELETE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/content/mod.rs b/apps/labrinth/src/routes/v3/content/mod.rs index 6b0a6b3802..a857e1f522 100644 --- a/apps/labrinth/src/routes/v3/content/mod.rs +++ b/apps/labrinth/src/routes/v3/content/mod.rs @@ -2,7 +2,7 @@ use super::ApiError; use crate::auth::checks::{ filter_visible_versions, is_visible_project, is_visible_version, }; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::validate::get_maybe_user_from_headers; use crate::database::models::ids::DBVersionId; use crate::database::models::version_item::VersionQueryResult; use crate::database::models::{DBProject, DBVersion}; @@ -12,6 +12,7 @@ use crate::models::projects::{DependencyType, Version}; use crate::models::users::User; use crate::queue::session::AuthQueue; use crate::util::error::ApiContext as _; +use crate::util::error::Context as _; use actix_web::{HttpRequest, post, web}; use ariadne::ids::base62_impl::parse_base62; use async_trait::async_trait; @@ -48,17 +49,16 @@ pub async fn resolve_content( redis: web::Data, session_queue: web::Data, ) -> Result, ApiError> { - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let cache_public_result = user_option.is_none(); let mut provider = LabrinthContentProvider { pool: pool.get_ref(), diff --git a/apps/labrinth/src/routes/v3/disclosures.rs b/apps/labrinth/src/routes/v3/disclosures.rs index 02095850eb..ae2afa0f0f 100644 --- a/apps/labrinth/src/routes/v3/disclosures.rs +++ b/apps/labrinth/src/routes/v3/disclosures.rs @@ -1,3 +1,4 @@ +use crate::auth::validate::get_maybe_user_from_headers; use actix_web::{HttpRequest, get, patch, web}; use chrono::Utc; use eyre::eyre; @@ -6,7 +7,7 @@ use utoipa::ToSchema; use xredis::RedisPool; use crate::auth::checks::{is_team_member_project, is_visible_project}; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database::{DBProject, models as db_models}; use crate::database::{PgPool, ReadOnlyPgPool}; use crate::models::disclosures::{ @@ -53,17 +54,16 @@ pub async fn get_project_disclosures( .wrap_internal_err("failed to fetch project")? .wrap_not_found_err("resource not found")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|(_, user)| user) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|(_, user)| user); if !is_visible_project(&project.inner, &user_option, &pool, false) .await @@ -142,7 +142,6 @@ pub async fn modify_project_disclosures( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/friends.rs b/apps/labrinth/src/routes/v3/friends.rs index 266bb26ab2..5be589a6ac 100644 --- a/apps/labrinth/src/routes/v3/friends.rs +++ b/apps/labrinth/src/routes/v3/friends.rs @@ -1,4 +1,4 @@ -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database::PgPool; use crate::database::models::blocked_user_item::DBBlockedUser; use crate::database::models::friend_item::DBFriend; @@ -43,7 +43,6 @@ pub async fn add_friend( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -194,7 +193,6 @@ pub async fn remove_friend( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -249,7 +247,6 @@ pub async fn friends( &redis, &session_queue, Scopes::USER_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/images.rs b/apps/labrinth/src/routes/v3/images.rs index bdccbcea3f..7e45da50c1 100644 --- a/apps/labrinth/src/routes/v3/images.rs +++ b/apps/labrinth/src/routes/v3/images.rs @@ -1,6 +1,6 @@ use super::threads::is_authorized_thread; use crate::auth::checks::{is_team_member_project, is_team_member_version}; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database; use crate::database::PgPool; use crate::database::models::{ @@ -69,7 +69,6 @@ pub async fn images_add( &redis, &session_queue, context.relevant_scope(), - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/limits.rs b/apps/labrinth/src/routes/v3/limits.rs index 40c524bede..ab11d37a8e 100644 --- a/apps/labrinth/src/routes/v3/limits.rs +++ b/apps/labrinth/src/routes/v3/limits.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use crate::database::PgPool; use crate::util::error::Context as _; use crate::{ @@ -30,7 +29,6 @@ pub async fn get_project_limits( &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -55,7 +53,6 @@ pub async fn get_organization_limits( &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -80,7 +77,6 @@ pub async fn get_collection_limits( &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/notifications.rs b/apps/labrinth/src/routes/v3/notifications.rs index 63216dddf5..90ca9829b4 100644 --- a/apps/labrinth/src/routes/v3/notifications.rs +++ b/apps/labrinth/src/routes/v3/notifications.rs @@ -1,4 +1,4 @@ -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database; use crate::database::PgPool; use crate::models::ids::NotificationId; @@ -54,7 +54,6 @@ pub async fn notifications_get( &redis, &session_queue, Scopes::NOTIFICATION_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -112,7 +111,6 @@ pub async fn notification_get( &redis, &session_queue, Scopes::NOTIFICATION_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -164,7 +162,6 @@ pub async fn notification_read( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -236,7 +233,6 @@ pub async fn notification_delete( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -312,7 +308,6 @@ pub async fn notifications_read( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -392,7 +387,6 @@ pub async fn notifications_delete( &redis, &session_queue, Scopes::NOTIFICATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/oauth_clients.rs b/apps/labrinth/src/routes/v3/oauth_clients.rs index 9784642d78..039045f28d 100644 --- a/apps/labrinth/src/routes/v3/oauth_clients.rs +++ b/apps/labrinth/src/routes/v3/oauth_clients.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use crate::util::error::ApiContext as _; use crate::util::error::Context as _; use std::{collections::HashSet, fmt::Display}; @@ -73,7 +72,6 @@ pub async fn get_user_clients( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -201,7 +199,6 @@ pub async fn oauth_client_create( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await? .1; @@ -270,7 +267,6 @@ pub async fn oauth_client_delete( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -341,7 +337,6 @@ pub async fn oauth_client_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -447,7 +442,6 @@ pub async fn oauth_client_icon_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -538,7 +532,6 @@ pub async fn oauth_client_icon_delete( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -605,7 +598,6 @@ pub async fn get_user_oauth_authorizations( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -645,7 +637,6 @@ pub async fn revoke_oauth_authorization( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/organizations.rs b/apps/labrinth/src/routes/v3/organizations.rs index 08084507c5..710b02bd43 100644 --- a/apps/labrinth/src/routes/v3/organizations.rs +++ b/apps/labrinth/src/routes/v3/organizations.rs @@ -1,11 +1,11 @@ +use crate::auth::validate::get_maybe_user_from_headers; use crate::util::error::ApiContext as _; use std::collections::HashMap; use super::ApiError; use crate::auth::checks::is_visible_organization; use crate::auth::{ - AccountLockRequirement, filter_visible_projects, get_user_from_headers, - require_verified_email, + filter_visible_projects, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; use crate::database::models::team_item::DBTeamMember; @@ -57,17 +57,16 @@ pub async fn organization_projects_get( session_queue: web::Data, ) -> Result { let id = info.into_inner().0; - let current_user = get_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::ORGANIZATION_READ | Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let organization_data = DBOrganization::get(&id, &**pool, &redis) .await @@ -139,7 +138,6 @@ pub async fn organization_create( &redis, &session_queue, Scopes::ORGANIZATION_CREATE, - AccountLockRequirement::NotLocked, ) .await? .1; @@ -241,17 +239,16 @@ pub async fn organization_get( session_queue: web::Data, ) -> Result { let id = info.into_inner().0; - let current_user = get_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::ORGANIZATION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let user_id = current_user.as_ref().map(|x| x.id.into()); let organization_data = DBOrganization::get(&id, &**pool, &redis) @@ -335,7 +332,6 @@ pub async fn organization_notes_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -454,17 +450,16 @@ pub async fn organizations_get( .await .wrap_internal_err("fetching users from database")?; - let current_user = get_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::ORGANIZATION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let user_id = current_user.as_ref().map(|x| x.id.into()); let include_notes = current_user.as_ref().is_some_and(|x| x.role.is_mod()); let notes = if include_notes { @@ -566,7 +561,6 @@ pub async fn organizations_edit( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -749,7 +743,6 @@ pub async fn organization_delete( &redis, &session_queue, Scopes::ORGANIZATION_DELETE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -939,7 +932,6 @@ pub async fn organization_projects_add( &redis, &session_queue, Scopes::PROJECT_WRITE | Scopes::ORGANIZATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1122,7 +1114,6 @@ pub async fn organization_projects_remove( &redis, &session_queue, Scopes::PROJECT_WRITE | Scopes::ORGANIZATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1336,7 +1327,6 @@ pub async fn organization_icon_edit( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1454,7 +1444,6 @@ pub async fn delete_organization_icon( &redis, &session_queue, Scopes::ORGANIZATION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/payouts.rs b/apps/labrinth/src/routes/v3/payouts.rs index ffcce07bb4..c199d1bff0 100644 --- a/apps/labrinth/src/routes/v3/payouts.rs +++ b/apps/labrinth/src/routes/v3/payouts.rs @@ -1,7 +1,5 @@ use crate::auth::validate::get_user_record_from_bearer_token; -use crate::auth::{ - AccountLockRequirement, AuthenticationError, get_user_from_headers, -}; +use crate::auth::{AuthenticationError, get_user_from_headers}; use crate::database::PgPool; use crate::database::models::DBUserId; use crate::database::models::{generate_payout_id, users_compliance}; @@ -72,7 +70,6 @@ pub async fn post_compliance_form( &redis, &session_queue, Scopes::PAYOUTS_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -524,7 +521,6 @@ pub async fn calculate_fees( &redis, &session_queue, false, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -564,7 +560,6 @@ pub async fn create_payout( &redis, &session_queue, false, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -792,7 +787,6 @@ pub async fn transaction_history( &redis, &session_queue, Scopes::PAYOUTS_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -884,7 +878,6 @@ pub async fn cancel_payout( &redis, &session_queue, Scopes::PAYOUTS_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1068,7 +1061,6 @@ pub async fn get_balance( &redis, &session_queue, Scopes::PAYOUTS_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/project_creation.rs b/apps/labrinth/src/routes/v3/project_creation.rs index 4d6e273639..1ab3433051 100644 --- a/apps/labrinth/src/routes/v3/project_creation.rs +++ b/apps/labrinth/src/routes/v3/project_creation.rs @@ -1,7 +1,6 @@ use super::version_creation::{InitialVersionData, try_create_version_fields}; use crate::auth::{ - AccountLockRequirement, AuthenticationError, get_user_from_headers, - require_verified_email, + AuthenticationError, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; use crate::database::PgTransaction; @@ -483,7 +482,6 @@ async fn project_create_inner( redis, session_queue, Scopes::PROJECT_CREATE, - AccountLockRequirement::NotLocked, ) .await?; diff --git a/apps/labrinth/src/routes/v3/project_creation/new.rs b/apps/labrinth/src/routes/v3/project_creation/new.rs index c3697c0705..2922375796 100644 --- a/apps/labrinth/src/routes/v3/project_creation/new.rs +++ b/apps/labrinth/src/routes/v3/project_creation/new.rs @@ -1,4 +1,3 @@ -use crate::auth::AccountLockRequirement; use actix_http::StatusCode; use actix_web::{HttpRequest, HttpResponse, ResponseError, put, web}; use eyre::eyre; @@ -137,7 +136,6 @@ pub async fn create( &redis, &session_queue, Scopes::PROJECT_CREATE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating project creator")?; diff --git a/apps/labrinth/src/routes/v3/projects/mod.rs b/apps/labrinth/src/routes/v3/projects/mod.rs index cb8e1f0c68..d35b459aa6 100644 --- a/apps/labrinth/src/routes/v3/projects/mod.rs +++ b/apps/labrinth/src/routes/v3/projects/mod.rs @@ -1,12 +1,11 @@ +use crate::auth::validate::get_maybe_user_from_headers; use crate::util::error::ApiContext as _; use std::any::type_name; use std::cmp::Reverse; use std::collections::HashMap; use crate::auth::checks::{filter_visible_versions, is_visible_project}; -use crate::auth::{ - AccountLockRequirement, filter_visible_projects, get_user_from_headers, -}; +use crate::auth::{filter_visible_projects, get_user_from_headers}; use crate::database::models::notification_item::NotificationBuilder; use crate::database::models::project_item::{ DBGalleryItem, DBModCategory, ProjectQueryResult, @@ -261,17 +260,16 @@ pub async fn projects_get( .await .wrap_api_err("fetching requested projects")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let projects = filter_visible_projects(projects_data, &user_option, &pool, false) @@ -309,17 +307,16 @@ pub async fn project_get_internal( let project_data = db_models::DBProject::get(&string, &**pool, &redis) .await .wrap_internal_err("failed to fetch project")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|(_, user)| user) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|(_, user)| user); if let Some(data) = project_data && is_visible_project(&data.inner, &user_option, &pool, false) @@ -461,7 +458,6 @@ pub async fn project_edit_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1749,17 +1745,16 @@ pub async fn dependency_list_internal( .await .wrap_api_err("fetching project from database")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(project) = result { if !is_visible_project(&project.inner, &user_option, &pool, false) @@ -1914,7 +1909,6 @@ pub async fn projects_edit( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2292,7 +2286,6 @@ pub async fn project_icon_edit_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2446,7 +2439,6 @@ pub async fn delete_project_icon_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2610,7 +2602,6 @@ pub async fn add_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -2842,7 +2833,6 @@ pub async fn edit_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3074,7 +3064,6 @@ pub async fn delete_gallery_item_internal( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3226,7 +3215,6 @@ pub async fn project_delete_internal( &redis, &session_queue, Scopes::PROJECT_DELETE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -3537,7 +3525,6 @@ pub async fn project_follow_internal( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3643,7 +3630,6 @@ pub async fn project_unfollow_internal( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -3725,17 +3711,16 @@ pub async fn project_get_organization( redis: web::Data, session_queue: web::Data, ) -> Result { - let current_user = get_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ | Scopes::ORGANIZATION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let user_id = current_user.as_ref().map(|x| x.id.into()); let string = info.into_inner().0; diff --git a/apps/labrinth/src/routes/v3/projects/validate.rs b/apps/labrinth/src/routes/v3/projects/validate.rs index 7b62ae0c47..877242e7b2 100644 --- a/apps/labrinth/src/routes/v3/projects/validate.rs +++ b/apps/labrinth/src/routes/v3/projects/validate.rs @@ -3,7 +3,7 @@ use eyre::eyre; use serde::Serialize; use xredis::RedisPool; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database::models::DBProjectId; use crate::database::models::project_item::ProjectQueryResult; use crate::database::{ @@ -102,7 +102,6 @@ pub async fn validate( &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::None, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/reports.rs b/apps/labrinth/src/routes/v3/reports.rs index 13bc236991..d3ad4136e0 100644 --- a/apps/labrinth/src/routes/v3/reports.rs +++ b/apps/labrinth/src/routes/v3/reports.rs @@ -1,7 +1,4 @@ -use crate::auth::{ - AccountLockRequirement, check_is_moderator_from_headers, - get_user_from_headers, -}; +use crate::auth::{check_is_moderator_from_headers, get_user_from_headers}; use crate::database; use crate::database::PgPool; use crate::database::models::SharedInstanceId; @@ -88,7 +85,6 @@ pub async fn report_create( &redis, &session_queue, Scopes::REPORT_CREATE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -414,7 +410,6 @@ pub async fn reports( &redis, &session_queue, Scopes::REPORT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -525,7 +520,6 @@ pub async fn reports_get( &redis, &session_queue, Scopes::REPORT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -565,7 +559,6 @@ pub async fn report_get( &redis, &session_queue, Scopes::REPORT_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -623,7 +616,6 @@ pub async fn report_edit( &redis, &session_queue, Scopes::REPORT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -761,7 +753,6 @@ pub async fn report_delete( &redis, &session_queue, Scopes::REPORT_DELETE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; diff --git a/apps/labrinth/src/routes/v3/teams.rs b/apps/labrinth/src/routes/v3/teams.rs index 25255b67f0..2b5319b098 100644 --- a/apps/labrinth/src/routes/v3/teams.rs +++ b/apps/labrinth/src/routes/v3/teams.rs @@ -1,5 +1,6 @@ use crate::auth::checks::{is_visible_organization, is_visible_project}; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; +use crate::auth::validate::get_maybe_user_from_headers; use crate::database::DBProject; use crate::database::PgPool; use crate::database::models::notification_item::NotificationBuilder; @@ -66,17 +67,16 @@ pub async fn team_members_get_project_internal( .wrap_api_err("fetching project from database")?; if let Some(project) = project_data { - let current_user = get_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, ¤t_user, &pool, false) .await @@ -156,17 +156,16 @@ pub async fn team_members_get_organization( .await .wrap_internal_err("fetching organization from database")?; - let current_user = get_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::ORGANIZATION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(organization) = organization_data && is_visible_organization(&organization, ¤t_user, &pool, &redis) @@ -257,17 +256,16 @@ pub async fn team_members_get( .await .wrap_internal_err("fetching users from database")?; - let current_user = get_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let user_id = current_user.as_ref().map(|x| x.id.into()); let logged_in = current_user @@ -349,17 +347,16 @@ pub async fn teams_get( .await .wrap_internal_err("fetching users from database")?; - let current_user = get_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let teams_groups = teams_data.into_iter().chunk_by(|data| data.team_id.0); @@ -422,7 +419,6 @@ pub async fn join_team( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -541,7 +537,6 @@ pub async fn add_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -828,7 +823,6 @@ pub async fn edit_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1042,7 +1036,6 @@ pub async fn transfer_ownership( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1258,7 +1251,6 @@ pub async fn remove_team_member( &redis, &session_queue, Scopes::PROJECT_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/threads.rs b/apps/labrinth/src/routes/v3/threads.rs index b0325087e0..4d7d82167d 100644 --- a/apps/labrinth/src/routes/v3/threads.rs +++ b/apps/labrinth/src/routes/v3/threads.rs @@ -1,4 +1,4 @@ -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database; use crate::database::PgPool; use crate::database::models::image_item; @@ -297,7 +297,6 @@ pub async fn thread_get( &redis, &session_queue, Scopes::THREAD_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -371,7 +370,6 @@ pub async fn threads_get( &redis, &session_queue, Scopes::THREAD_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -429,7 +427,6 @@ pub async fn thread_send_message( &redis, &session_queue, Scopes::THREAD_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -700,7 +697,6 @@ pub async fn message_delete( &redis, &session_queue, Scopes::THREAD_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/users.rs b/apps/labrinth/src/routes/v3/users.rs index 22356bf083..84ea2bb6fd 100644 --- a/apps/labrinth/src/routes/v3/users.rs +++ b/apps/labrinth/src/routes/v3/users.rs @@ -1,5 +1,5 @@ -use crate::auth::AccountLockRequirement; -use crate::auth::validate::get_full_user_from_headers; +use crate::auth::validate::get_full_user_from_headers_allow_locked; +use crate::auth::validate::get_maybe_user_from_headers; use crate::util::error::ApiContext as _; use std::{ cmp::Reverse, @@ -82,17 +82,16 @@ pub async fn all_projects( redis: web::Data, session_queue: web::Data, ) -> Result, ApiError> { - let user = get_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let target_user = DBUser::get(&info.into_inner().0, &**pool, &redis) .await .wrap_internal_err("fetching user from database")? @@ -237,7 +236,6 @@ pub async fn admin_user_email( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .map(|x| x.1) @@ -298,17 +296,16 @@ pub async fn projects_list( redis: web::Data, session_queue: web::Data, ) -> Result { - let user = get_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis) .await @@ -352,13 +349,12 @@ pub async fn user_auth_get( redis: web::Data, session_queue: web::Data, ) -> Result { - let (scopes, db_user) = get_full_user_from_headers( + let (scopes, db_user) = get_full_user_from_headers_allow_locked( &req, &**pool, &redis, &session_queue, Scopes::USER_READ, - AccountLockRequirement::None, ) .await .wrap_auth_err("authenticating API request")?; @@ -405,7 +401,6 @@ pub async fn get_user_preferences( &redis, &session_queue, Scopes::USER_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -450,7 +445,6 @@ pub async fn edit_user_preferences( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -555,25 +549,18 @@ pub async fn users_get( .await .wrap_internal_err("fetching users from database")?; - let auth_user = get_full_user_from_headers( + let auth_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::None, ) .await - .ok(); + .wrap_auth_err("authenticating API request")?; let is_mod = auth_user.as_ref().is_some_and(|(scopes, user)| { - match user.account_locked { - false => { - scopes.contains(Scopes::SESSION_ACCESS) - && Role::from_string(&user.role).is_mod() - } - true => false, - } + scopes.contains(Scopes::SESSION_ACCESS) && user.role.is_mod() }); let notes = if is_mod { DBModerationNote::get_many_users( @@ -594,8 +581,7 @@ pub async fn users_get( let visible_account_locked = auth_user .as_ref() .filter(|(_, viewer)| { - viewer.id == user_id - || Role::from_string(&viewer.role).is_admin() + viewer.id == user_id.into() || viewer.role.is_admin() }) .map(|_| data.account_locked); let mut user = User::from(data); @@ -635,34 +621,28 @@ pub async fn user_get( .wrap_internal_err("fetching user from database")?; if let Some(data) = user_data { - let auth_user = get_full_user_from_headers( + let auth_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::empty(), - AccountLockRequirement::None, ) .await - .ok(); - - let staff_role = - auth_user.as_ref().and_then(|(scopes, user)| { - match user.account_locked { - false => scopes - .contains(Scopes::SESSION_ACCESS) - .then(|| Role::from_string(&user.role)), - true => None, - } - }); - let is_admin = staff_role.as_ref().is_some_and(Role::is_admin); - let is_mod = staff_role.as_ref().is_some_and(Role::is_mod); + .wrap_auth_err("authenticating API request")?; + + let staff_role = auth_user.as_ref().and_then(|(scopes, user)| { + scopes + .contains(Scopes::SESSION_ACCESS) + .then_some(&user.role) + }); + let is_admin = staff_role.is_some_and(Role::is_admin); + let is_mod = staff_role.is_some_and(Role::is_mod); let user_id = data.id; let visible_account_locked = auth_user .as_ref() .filter(|(_, viewer)| { - viewer.id == user_id - || Role::from_string(&viewer.role).is_admin() + viewer.id == user_id.into() || viewer.role.is_admin() }) .map(|_| data.account_locked); @@ -711,7 +691,6 @@ pub async fn user_notes_edit( &redis, &session_queue, Scopes::SESSION_ACCESS, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -791,17 +770,16 @@ pub async fn collections_list( redis: web::Data, session_queue: web::Data, ) -> Result { - let user = get_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::COLLECTION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis) .await @@ -839,17 +817,16 @@ pub async fn orgs_list( redis: web::Data, session_queue: web::Data, ) -> Result { - let user = get_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis) .await @@ -983,7 +960,6 @@ pub async fn user_edit( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")?; @@ -1222,7 +1198,6 @@ pub async fn user_icon_edit( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1318,7 +1293,6 @@ pub async fn user_icon_delete( &redis, &session_queue, Scopes::USER_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1390,7 +1364,6 @@ pub async fn user_delete( &redis, &session_queue, Scopes::USER_DELETE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1454,7 +1427,6 @@ pub async fn user_follows( &redis, &session_queue, Scopes::USER_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1515,7 +1487,6 @@ pub async fn user_notifications( &redis, &session_queue, Scopes::NOTIFICATION_READ, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/src/routes/v3/version_creation.rs b/apps/labrinth/src/routes/v3/version_creation.rs index 672d9ac790..ea375ff76c 100644 --- a/apps/labrinth/src/routes/v3/version_creation.rs +++ b/apps/labrinth/src/routes/v3/version_creation.rs @@ -1,5 +1,5 @@ use super::project_creation::{CreateError, UploadedFile}; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database::PgPool; use crate::database::PgTransaction; use crate::database::models::loader_fields::{ @@ -223,7 +223,6 @@ async fn version_create_inner( redis, session_queue, Scopes::VERSION_CREATE, - AccountLockRequirement::NotLocked, ) .await? .1; @@ -706,7 +705,6 @@ async fn upload_file_to_version_inner( &redis, session_queue, Scopes::VERSION_WRITE, - AccountLockRequirement::NotLocked, ) .await? .1; diff --git a/apps/labrinth/src/routes/v3/version_file.rs b/apps/labrinth/src/routes/v3/version_file.rs index 7681c0d8f8..edd2631dd6 100644 --- a/apps/labrinth/src/routes/v3/version_file.rs +++ b/apps/labrinth/src/routes/v3/version_file.rs @@ -1,8 +1,7 @@ use super::ApiError; use crate::auth::checks::{filter_visible_versions, is_visible_version}; -use crate::auth::{ - AccountLockRequirement, filter_visible_projects, get_user_from_headers, -}; +use crate::auth::validate::get_maybe_user_from_headers; +use crate::auth::{filter_visible_projects, get_user_from_headers}; use crate::database::PgPool; use crate::database::ReadOnlyPgPool; use crate::models::ids::VersionId; @@ -74,17 +73,16 @@ pub async fn get_version_from_hash( hash_query: web::Query, session_queue: web::Data, ) -> Result { - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let hash = info.into_inner().0.to_lowercase(); let algorithm = hash_query.algorithm.clone().unwrap_or_else(|| { default_algorithm_from_hashes(std::slice::from_ref(&hash)) @@ -207,17 +205,16 @@ pub async fn get_update_from_hash( update_data: web::Json, session_queue: web::Data, ) -> Result { - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &***pool, &redis, &session_queue, Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let hash = info.into_inner().0.to_lowercase(); if let Some(file) = database::models::DBVersion::get_file_from_hash( hash_query.algorithm.clone().unwrap_or_else(|| { @@ -331,17 +328,16 @@ pub async fn get_versions_from_hashes( file_data: web::Json, session_queue: web::Data, ) -> Result { - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &***pool, &redis, &session_queue, Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let algorithm = file_data .algorithm @@ -415,17 +411,16 @@ pub async fn get_projects_from_hashes( file_data: web::Json, session_queue: web::Data, ) -> Result { - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let algorithm = file_data .algorithm @@ -697,17 +692,16 @@ pub async fn update_individual_files( update_data: web::Json, session_queue: web::Data, ) -> Result { - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let algorithm = update_data.algorithm.clone().unwrap_or_else(|| { default_algorithm_from_hashes( @@ -864,7 +858,6 @@ pub async fn delete_file( &redis, &session_queue, Scopes::VERSION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1043,17 +1036,16 @@ pub async fn download_version( hash_query: web::Query, session_queue: web::Data, ) -> Result { - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let hash = info.into_inner().0.to_lowercase(); let algorithm = hash_query.algorithm.clone().unwrap_or_else(|| { diff --git a/apps/labrinth/src/routes/v3/versions.rs b/apps/labrinth/src/routes/v3/versions.rs index 9ac5604ea5..cfa38b7c15 100644 --- a/apps/labrinth/src/routes/v3/versions.rs +++ b/apps/labrinth/src/routes/v3/versions.rs @@ -1,3 +1,4 @@ +use crate::auth::validate::get_maybe_user_from_headers; use crate::util::error::ApiContext as _; use std::collections::HashMap; @@ -5,7 +6,7 @@ use super::ApiError; use crate::auth::checks::{ filter_visible_versions, is_visible_project, is_visible_version, }; -use crate::auth::{AccountLockRequirement, get_user_from_headers}; +use crate::auth::get_user_from_headers; use crate::database; use crate::database::models::loader_fields::{ self, LoaderField, LoaderFieldEnumValue, VersionField, @@ -84,17 +85,16 @@ pub async fn version_project_get_helper( .await .wrap_api_err("fetching project from database")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(project) = result { if !is_visible_project(&project.inner, &user_option, &pool, false) @@ -212,17 +212,16 @@ pub async fn versions_get( .await .wrap_internal_err("fetching versions from database")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let mut versions = filter_visible_versions( versions_data, @@ -295,17 +294,16 @@ pub async fn version_get_helper( .await .wrap_internal_err("fetching version from database")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(data) = version_data && is_visible_version(&data.inner, &user_option, &pool, &redis) @@ -473,7 +471,6 @@ pub async fn version_edit_helper( &redis, &session_queue, Scopes::VERSION_WRITE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? @@ -1031,17 +1028,16 @@ pub async fn version_list_internal( .await .wrap_api_err("fetching project from database")?; - let user_option = get_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, Scopes::PROJECT_READ | Scopes::VERSION_READ, - AccountLockRequirement::NotLocked, ) .await - .map(|x| x.1) - .ok(); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(project) = result { if !is_visible_project(&project.inner, &user_option, &pool, false) @@ -1236,7 +1232,6 @@ pub async fn version_delete( &redis, &session_queue, Scopes::VERSION_DELETE, - AccountLockRequirement::NotLocked, ) .await .wrap_auth_err("authenticating API request")? diff --git a/apps/labrinth/tests/account_locked.rs b/apps/labrinth/tests/account_locked.rs index 0bd11d4ef2..d476d2045d 100644 --- a/apps/labrinth/tests/account_locked.rs +++ b/apps/labrinth/tests/account_locked.rs @@ -16,7 +16,11 @@ use common::{ }; use labrinth::{ auth::{ - AccountLockRequirement, AuthenticationError, get_user_from_headers, + AuthenticationError, get_user_from_headers, + validate::{ + get_full_user_from_headers_allow_locked, + get_maybe_user_from_headers, get_user_record_from_bearer_token, + }, }, database::models::{ DBUserId, flow_item::DBFlow, session_item::SessionBuilder, @@ -82,6 +86,9 @@ async fn account_locked_is_visible_only_to_self_and_admin_in_both_api_versions() (Some(restricted_tokens[0].as_str()), true), (Some(restricted_tokens[1].as_str()), true), ] { + let locked_viewer = account_locked + && (pat == USER_USER_PAT + || pat == Some(restricted_tokens[0].as_str())); for target in ["3", "User"] { let response = env .call( @@ -91,6 +98,10 @@ async fn account_locked_is_visible_only_to_self_and_admin_in_both_api_versions() .to_request(), ) .await; + if locked_viewer { + assert_status!(&response, StatusCode::FORBIDDEN); + continue; + } assert_status!(&response, StatusCode::OK); let body: Value = test::read_body_json(response).await; assert_eq!( @@ -130,6 +141,10 @@ async fn account_locked_is_visible_only_to_self_and_admin_in_both_api_versions() .to_request(), ) .await; + if locked_viewer { + assert_status!(&response, StatusCode::FORBIDDEN); + continue; + } assert_status!(&response, StatusCode::OK); let body: Vec = test::read_body_json(response).await; assert_eq!(body.len(), 2); @@ -171,6 +186,10 @@ async fn account_locked_is_visible_only_to_self_and_admin_in_both_api_versions() "stripe_customer_id", "allow_friend_requests", "eligibility_verified_at", + "github_id", + "discord_id", + "steam_id", + "moderation_notes", ] { assert!( body[field].is_null(), @@ -234,47 +253,87 @@ async fn existing_pat_session_and_oauth_credentials_obey_account_locks_and_cache ENV.RATE_LIMIT_IGNORE_KEY.as_str(), )) .to_http_request(); - for requirement in [ - AccountLockRequirement::None, - AccountLockRequirement::NotLocked, - ] { - let result = get_user_from_headers( - &req, - &*env.db.pool, - &env.db.redis_pool, - &queue, - Scopes::USER_READ, - requirement, + let result = get_user_from_headers( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::USER_READ, + ) + .await; + match account_locked { + true => assert!(matches!( + result, + Err(AuthenticationError::AccountLocked) + )), + false => assert_eq!( + result.unwrap().1.account_locked, + Some(false) + ), + } + let (_, user) = get_full_user_from_headers_allow_locked( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::USER_READ, + ) + .await + .unwrap(); + assert_eq!(user.account_locked, account_locked); + let response = env + .call( + test::TestRequest::get() + .uri("/v3/user") + .append_pat(Some(token)) + .append_header(( + "x-ratelimit-key", + ENV.RATE_LIMIT_IGNORE_KEY.as_str(), + )) + .to_request(), ) .await; - match (account_locked, requirement) { - (true, AccountLockRequirement::NotLocked) => { - assert!(matches!( - result, - Err(AuthenticationError::AccountLocked) - )); - } - (false, AccountLockRequirement::NotLocked) - | (_, AccountLockRequirement::None) => { - assert_eq!( - result.unwrap().1.account_locked, - Some(account_locked) - ); - } - } + assert_status!(&response, StatusCode::OK); + let body: Value = test::read_body_json(response).await; + assert_eq!(body["account_locked"], json!(account_locked)); + if account_locked { + assert!(body["auth_providers"].is_null()); + assert!(body["email"].is_null()); + assert!(matches!( + get_maybe_user_from_headers( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::SESSION_ACCESS, + ) + .await, + Err(AuthenticationError::AccountLocked) + )); + assert!(matches!( + get_user_record_from_bearer_token( + &req, + Some(token), + &*env.db.pool, + &env.db.redis_pool, + &queue, + true, + ) + .await, + Err(AuthenticationError::AccountLocked) + )); } } } let req = test::TestRequest::get() .append_pat(Some(&oauth_token)) .to_http_request(); - let result = get_user_from_headers( + let result = get_full_user_from_headers_allow_locked( &req, &*env.db.pool, &env.db.redis_pool, &queue, Scopes::USER_AUTH_WRITE, - AccountLockRequirement::None, ) .await; assert!(matches!( @@ -296,7 +355,15 @@ async fn locked_accounts_cannot_mutate_resources_read_sensitive_data_or_unlock_t } set_account_locked(&env, "3", true).await; let project = format!("/v3/project/{}", env.dummy.project_alpha.project_id); + let validation = format!("{project}/validate"); for (method, uri, body) in [ + (Method::GET, project.as_str(), json!(null)), + (Method::GET, validation.as_str(), json!(null)), + (Method::GET, "/v3/user/3/projects", json!(null)), + (Method::GET, "/v3/organizations?ids=%5B%5D", json!(null)), + (Method::GET, "/v3/collections?ids=%5B%5D", json!(null)), + (Method::GET, "/v3/versions?ids=%5B%5D", json!(null)), + (Method::POST, "/analytics/view", json!({"url":"https://modrinth.com/"})), (Method::PATCH, "/v3/user/3", json!({"account_locked":false})), (Method::PATCH, project.as_str(), json!({"title":"changed"})), (Method::PATCH, "/v3/organization/missing", json!({"name":"changed"})), @@ -312,10 +379,26 @@ async fn locked_accounts_cannot_mutate_resources_read_sensitive_data_or_unlock_t let body: Value = test::read_body_json(response).await; assert_eq!(body["error"], "auth_error", "{uri}"); } + for pat in [None, Some("mrp_invalid")] { + let response = env.call(test::TestRequest::get().uri(&project) + .append_pat(pat).to_request()).await; + assert_status!(&response, StatusCode::OK); + } set_account_locked(&env, "1", true).await; let response = env.call(test::TestRequest::patch().uri("/v3/user/3") .append_pat(ADMIN_USER_PAT).set_json(json!({"account_locked":false})).to_request()).await; assert_status!(&response, StatusCode::FORBIDDEN); + for version in ["v2", "v3"] { + let response = env.call(test::TestRequest::get() + .uri(&format!("/{version}/user")) + .append_pat(ADMIN_USER_PAT).to_request()).await; + assert_status!(&response, StatusCode::OK); + let body: Value = test::read_body_json(response).await; + assert_eq!(body["account_locked"], true); + for field in ["email", "auth_providers", "github_id", "discord_id", "steam_id", "payout_data", "moderation_notes"] { + assert!(body[field].is_null(), "{field} must not be exposed to a locked admin"); + } + } }).await; } From ae60ea13e871883db23614e787145854e4cc8160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=C3=A7ois-X=2E=20T=2E?= Date: Sat, 5 Sep 2026 01:27:27 -0400 Subject: [PATCH 4/4] chore: fmt --- apps/labrinth/src/auth/mod.rs | 12 +- apps/labrinth/src/auth/oauth/errors.rs | 10 +- apps/labrinth/src/auth/oauth/mod.rs | 8 +- apps/labrinth/src/auth/templates/mod.rs | 2 +- apps/labrinth/src/auth/validate.rs | 108 +++--- .../labrinth/src/database/models/user_item.rs | 2 +- .../src/database/models/users_redeemals.rs | 4 +- apps/labrinth/src/models/v2/user.rs | 8 +- apps/labrinth/src/models/v3/users.rs | 8 +- apps/labrinth/src/routes/analytics.rs | 10 +- apps/labrinth/src/routes/internal/admin.rs | 18 +- .../labrinth/src/routes/internal/affiliate.rs | 6 +- apps/labrinth/src/routes/internal/flows.rs | 68 ++-- apps/labrinth/src/routes/internal/medal.rs | 20 +- apps/labrinth/src/routes/internal/privacy.rs | 8 +- apps/labrinth/src/routes/maven.rs | 24 +- apps/labrinth/src/routes/mod.rs | 52 +-- apps/labrinth/src/routes/updates.rs | 6 +- apps/labrinth/src/routes/v2/users.rs | 4 +- apps/labrinth/src/routes/v3/collections.rs | 14 +- apps/labrinth/src/routes/v3/content/mod.rs | 6 +- apps/labrinth/src/routes/v3/disclosures.rs | 6 +- apps/labrinth/src/routes/v3/organizations.rs | 20 +- .../src/routes/v3/project_creation.rs | 2 +- apps/labrinth/src/routes/v3/projects/mod.rs | 24 +- apps/labrinth/src/routes/v3/teams.rs | 24 +- apps/labrinth/src/routes/v3/users.rs | 162 ++++---- apps/labrinth/src/routes/v3/version_file.rs | 36 +- apps/labrinth/src/routes/v3/versions.rs | 24 +- apps/labrinth/tests/account_locked.rs | 346 +++++++++--------- 30 files changed, 522 insertions(+), 520 deletions(-) diff --git a/apps/labrinth/src/auth/mod.rs b/apps/labrinth/src/auth/mod.rs index def5440a80..03f4f7dfad 100644 --- a/apps/labrinth/src/auth/mod.rs +++ b/apps/labrinth/src/auth/mod.rs @@ -9,8 +9,8 @@ pub use checks::{ }; use serde::{Deserialize, Serialize}; pub use validate::{ - check_account_unlocked, check_is_moderator_from_headers, - get_user_from_bearer_token, get_user_from_headers, + check_account_unlocked, check_is_moderator_from_headers, + get_user_from_bearer_token, get_user_from_headers, }; use crate::file_hosting::FileHostingError; @@ -39,8 +39,8 @@ pub enum AuthenticationError { Mail(#[from] crate::queue::email::MailError), #[error("Invalid Authentication Credentials")] InvalidCredentials, - #[error("account is locked")] - AccountLocked, + #[error("account is locked")] + AccountLocked, #[error("Authentication method was not valid")] InvalidAuthMethod, #[error("GitHub Token from incorrect Client ID")] @@ -76,7 +76,7 @@ impl actix_web::ResponseError for AuthenticationError { StatusCode::INTERNAL_SERVER_ERROR } AuthenticationError::InvalidCredentials => StatusCode::UNAUTHORIZED, - AuthenticationError::AccountLocked => StatusCode::FORBIDDEN, + AuthenticationError::AccountLocked => StatusCode::FORBIDDEN, AuthenticationError::Decoding(..) => StatusCode::BAD_REQUEST, AuthenticationError::Mail(..) => StatusCode::INTERNAL_SERVER_ERROR, AuthenticationError::InvalidAuthMethod => StatusCode::UNAUTHORIZED, @@ -112,7 +112,7 @@ impl AuthenticationError { AuthenticationError::SerDe(..) => "invalid_input", AuthenticationError::Reqwest(..) => "network_error", AuthenticationError::InvalidCredentials => "invalid_credentials", - AuthenticationError::AccountLocked => "account_locked", + AuthenticationError::AccountLocked => "account_locked", AuthenticationError::Decoding(..) => "decoding_error", AuthenticationError::Mail(..) => "mail_error", AuthenticationError::InvalidAuthMethod => "invalid_auth_method", diff --git a/apps/labrinth/src/auth/oauth/errors.rs b/apps/labrinth/src/auth/oauth/errors.rs index d100134d5e..15673c5fd6 100644 --- a/apps/labrinth/src/auth/oauth/errors.rs +++ b/apps/labrinth/src/auth/oauth/errors.rs @@ -58,9 +58,9 @@ impl OAuthError { impl actix_web::ResponseError for OAuthError { fn status_code(&self) -> StatusCode { match *self.error_type { - OAuthErrorType::AuthenticationError( - AuthenticationError::AccountLocked, - ) if self.valid_redirect_uri.is_none() => StatusCode::FORBIDDEN, + OAuthErrorType::AuthenticationError( + AuthenticationError::AccountLocked, + ) if self.valid_redirect_uri.is_none() => StatusCode::FORBIDDEN, OAuthErrorType::AuthenticationError(_) | OAuthErrorType::FailedScopeParse(_) | OAuthErrorType::ScopesTooBroad @@ -177,7 +177,9 @@ impl OAuthErrorType { match self { Self::RedirectUriNotConfigured(_) | Self::ClientMissingRedirectURI { client_id: _ } => "invalid_uri", - Self::AuthenticationError(AuthenticationError::AccountLocked) => "access_denied", + Self::AuthenticationError(AuthenticationError::AccountLocked) => { + "access_denied" + } Self::AuthenticationError(_) | Self::InvalidAcceptFlowId => { "server_error" } diff --git a/apps/labrinth/src/auth/oauth/mod.rs b/apps/labrinth/src/auth/oauth/mod.rs index 9d02719e90..55595c997b 100644 --- a/apps/labrinth/src/auth/oauth/mod.rs +++ b/apps/labrinth/src/auth/oauth/mod.rs @@ -313,10 +313,10 @@ pub async fn request_token( user_id, } .insert(&mut transaction) - .await? - .ok_or_else(|| { - OAuthError::error(OAuthErrorType::InvalidAuthCode) - })?; + .await? + .ok_or_else(|| { + OAuthError::error(OAuthErrorType::InvalidAuthCode) + })?; transaction.commit().await?; diff --git a/apps/labrinth/src/auth/templates/mod.rs b/apps/labrinth/src/auth/templates/mod.rs index a0a8b08c1a..9ac1b801e1 100644 --- a/apps/labrinth/src/auth/templates/mod.rs +++ b/apps/labrinth/src/auth/templates/mod.rs @@ -40,7 +40,7 @@ impl Display for ErrorPage { impl ErrorPage { pub fn render(&self) -> HttpResponse { - HttpResponse::build(self.code) + HttpResponse::build(self.code) .append_header(("Content-Type", "text/html; charset=utf-8")) .body(self.to_string()) } diff --git a/apps/labrinth/src/auth/validate.rs b/apps/labrinth/src/auth/validate.rs index fe32c2531c..bf14b2bf14 100644 --- a/apps/labrinth/src/auth/validate.rs +++ b/apps/labrinth/src/auth/validate.rs @@ -12,12 +12,12 @@ use chrono::Utc; use xredis::RedisPool; pub fn check_account_unlocked( - account_locked: bool, + account_locked: bool, ) -> Result<(), AuthenticationError> { - match account_locked { - true => Err(AuthenticationError::AccountLocked), - false => Ok(()), - } + match account_locked { + true => Err(AuthenticationError::AccountLocked), + false => Ok(()), + } } /// Allows anonymous or invalid credentials, but rejects locked accounts. @@ -35,20 +35,20 @@ where return Ok(None); } - match get_user_from_headers( + match get_user_from_headers( req, executor, redis, session_queue, - required_scopes, + required_scopes, ) - .await - { - Ok(user) => Ok(Some(user)), - Err(AuthenticationError::AccountLocked) => { - Err(AuthenticationError::AccountLocked) - } - Err(_) => Ok(None), + .await + { + Ok(user) => Ok(Some(user)), + Err(AuthenticationError::AccountLocked) => { + Err(AuthenticationError::AccountLocked) + } + Err(_) => Ok(None), } } @@ -69,34 +69,34 @@ where redis, session_queue, false, - ) - .await? - .ok_or(AuthenticationError::InvalidCredentials)?; - if !scopes.contains(required_scopes) { - return Err(AuthenticationError::InvalidCredentials); - } - Ok((scopes, db_user)) + ) + .await? + .ok_or(AuthenticationError::InvalidCredentials)?; + if !scopes.contains(required_scopes) { + return Err(AuthenticationError::InvalidCredentials); + } + Ok((scopes, db_user)) } /// Authenticates without rejecting locked accounts. The caller must check /// `account_locked` before exposing private data or allowing other actions. pub async fn get_full_user_from_headers_allow_locked<'a, E>( - req: &HttpRequest, - executor: E, - redis: &RedisPool, - session_queue: &AuthQueue, - required_scopes: Scopes, + req: &HttpRequest, + executor: E, + redis: &RedisPool, + session_queue: &AuthQueue, + required_scopes: Scopes, ) -> Result<(Scopes, DBUser), AuthenticationError> where - E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, + E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, { - let (scopes, db_user) = get_user_record_from_bearer_token_allow_locked( - req, - None, - executor, - redis, - session_queue, - false, + let (scopes, db_user) = get_user_record_from_bearer_token_allow_locked( + req, + None, + executor, + redis, + session_queue, + false, ) .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; @@ -165,30 +165,30 @@ pub async fn get_user_record_from_bearer_token<'a, E>( allow_expired: bool, ) -> Result, AuthenticationError> where - E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, + E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, { - let user = get_user_record_from_bearer_token_allow_locked( - req, - token, - executor, - redis, - session_queue, - allow_expired, - ) - .await?; - if let Some((_, user)) = &user { - check_account_unlocked(user.account_locked)?; - } - Ok(user) + let user = get_user_record_from_bearer_token_allow_locked( + req, + token, + executor, + redis, + session_queue, + allow_expired, + ) + .await?; + if let Some((_, user)) = &user { + check_account_unlocked(user.account_locked)?; + } + Ok(user) } async fn get_user_record_from_bearer_token_allow_locked<'a, E>( - req: &HttpRequest, - token: Option<&str>, - executor: E, - redis: &RedisPool, - session_queue: &AuthQueue, - allow_expired: bool, + req: &HttpRequest, + token: Option<&str>, + executor: E, + redis: &RedisPool, + session_queue: &AuthQueue, + allow_expired: bool, ) -> Result, AuthenticationError> where E: crate::database::Executor<'a, Database = sqlx::Postgres> + Copy, diff --git a/apps/labrinth/src/database/models/user_item.rs b/apps/labrinth/src/database/models/user_item.rs index 805dcd06e7..a4161bba92 100644 --- a/apps/labrinth/src/database/models/user_item.rs +++ b/apps/labrinth/src/database/models/user_item.rs @@ -52,7 +52,7 @@ pub struct DBUser { pub bio: Option, pub created: DateTime, pub role: String, - pub account_locked: bool, + pub account_locked: bool, pub badges: Badges, #[serde(default)] pub campaign_pride_26: Option, diff --git a/apps/labrinth/src/database/models/users_redeemals.rs b/apps/labrinth/src/database/models/users_redeemals.rs index a130a5b9d9..fa3f58ed19 100644 --- a/apps/labrinth/src/database/models/users_redeemals.rs +++ b/apps/labrinth/src/database/models/users_redeemals.rs @@ -244,7 +244,7 @@ impl UserRedeemal { #[derive(Debug)] pub struct RedeemalLookupFields { pub user_id: DBUserId, - pub account_locked: bool, + pub account_locked: bool, pub redeemal_status: Option, } @@ -292,7 +292,7 @@ impl RedeemalLookupFields { Ok(maybe_row.map(|row| RedeemalLookupFields { user_id: DBUserId(row.id), - account_locked: row.account_locked, + account_locked: row.account_locked, redeemal_status: row .status .as_deref() diff --git a/apps/labrinth/src/models/v2/user.rs b/apps/labrinth/src/models/v2/user.rs index c309a3e2ce..6ad98a2291 100644 --- a/apps/labrinth/src/models/v2/user.rs +++ b/apps/labrinth/src/models/v2/user.rs @@ -1,6 +1,6 @@ use crate::{ auth::AuthProvider, - models::users::{Badges, Role, UserPayoutData}, + models::users::{Badges, Role, UserPayoutData}, }; use ariadne::ids::UserId; use chrono::{DateTime, Utc}; @@ -15,8 +15,8 @@ pub struct LegacyUser { pub bio: Option, pub created: DateTime, pub role: Role, - #[serde(skip_serializing_if = "Option::is_none")] - pub account_locked: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub account_locked: Option, pub badges: Badges, pub auth_providers: Option>, // this was changed in v3, but not changes ones we want to keep out of v2 @@ -41,7 +41,7 @@ impl From for LegacyUser { bio: data.bio, created: data.created, role: data.role, - account_locked: data.account_locked, + account_locked: data.account_locked, badges: data.badges, payout_data: data.payout_data, auth_providers: data.auth_providers, diff --git a/apps/labrinth/src/models/v3/users.rs b/apps/labrinth/src/models/v3/users.rs index aac1b3dd4a..3f167877c0 100644 --- a/apps/labrinth/src/models/v3/users.rs +++ b/apps/labrinth/src/models/v3/users.rs @@ -55,8 +55,8 @@ pub struct User { pub bio: Option, pub created: DateTime, pub role: Role, - #[serde(skip_serializing_if = "Option::is_none")] - pub account_locked: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub account_locked: Option, pub badges: Badges, pub campaigns: UserCampaigns, @@ -115,7 +115,7 @@ impl From for User { bio: data.bio, created: data.created, role: Role::from_string(&data.role), - account_locked: None, + account_locked: None, badges: data.badges, campaigns: UserCampaigns { pride_26: data.campaign_pride_26, @@ -180,7 +180,7 @@ impl User { bio: db_user.bio, created: db_user.created, role: Role::from_string(&db_user.role), - account_locked: Some(db_user.account_locked), + account_locked: Some(db_user.account_locked), badges: db_user.badges, campaigns: UserCampaigns { pride_26: db_user.campaign_pride_26, diff --git a/apps/labrinth/src/routes/analytics.rs b/apps/labrinth/src/routes/analytics.rs index 16a1b268a3..da289a65f5 100644 --- a/apps/labrinth/src/routes/analytics.rs +++ b/apps/labrinth/src/routes/analytics.rs @@ -75,7 +75,7 @@ pub async fn page_view_ingest( pool: web::Data, redis: web::Data, ) -> Result { - let user = get_maybe_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -83,7 +83,7 @@ pub async fn page_view_ingest( Scopes::empty(), ) .await - .wrap_auth_err("authenticating API request")?; + .wrap_auth_err("authenticating API request")?; let conn_info = req.connection_info().peer_addr().map(|x| x.to_string()); let url = Url::parse(&url_input.url) @@ -291,7 +291,7 @@ pub async fn minecraft_server_play_ingest( redis: web::Data, http: web::Data, ) -> Result<(), ApiError> { - let user = get_maybe_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -299,8 +299,8 @@ pub async fn minecraft_server_play_ingest( Scopes::empty(), ) .await - .wrap_auth_err("authenticating API request")? - .map(|(_, user)| user); + .wrap_auth_err("authenticating API request")? + .map(|(_, user)| user); let project_id = play_input.project_id; diff --git a/apps/labrinth/src/routes/internal/admin.rs b/apps/labrinth/src/routes/internal/admin.rs index c644d70d83..7e8c1978e2 100644 --- a/apps/labrinth/src/routes/internal/admin.rs +++ b/apps/labrinth/src/routes/internal/admin.rs @@ -162,7 +162,7 @@ pub async fn count_download( .find(|x| x.0.to_lowercase() == "authorization") .map(|x| &**x.1); - let user = match get_user_record_from_bearer_token( + let user = match get_user_record_from_bearer_token( &req, token, &**pool, @@ -171,14 +171,14 @@ pub async fn count_download( false, ) .await - { - Ok(user) => user, - Err(AuthenticationError::AccountLocked) => { - return Err(AuthenticationError::AccountLocked) - .wrap_auth_err("authenticating API request"); - } - Err(_) => None, - }; + { + Ok(user) => user, + Err(AuthenticationError::AccountLocked) => { + return Err(AuthenticationError::AccountLocked) + .wrap_auth_err("authenticating API request"); + } + Err(_) => None, + }; let project_id: crate::database::models::ids::DBProjectId = download_body.project_id.into(); diff --git a/apps/labrinth/src/routes/internal/affiliate.rs b/apps/labrinth/src/routes/internal/affiliate.rs index 9ff53c99c7..413a86c461 100644 --- a/apps/labrinth/src/routes/internal/affiliate.rs +++ b/apps/labrinth/src/routes/internal/affiliate.rs @@ -54,7 +54,7 @@ pub async fn ingest_click( session_queue: web::Data, analytics_queue: web::Data>, ) -> Result<(), ApiError> { - let user = get_maybe_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -62,8 +62,8 @@ pub async fn ingest_click( Scopes::empty(), ) .await - .wrap_auth_err("authenticating API request")? - .map(|(_, user)| user); + .wrap_auth_err("authenticating API request")? + .map(|(_, user)| user); let conn_info = req.connection_info().peer_addr().map(|x| x.to_string()); let url = ingest_click.url; diff --git a/apps/labrinth/src/routes/internal/flows.rs b/apps/labrinth/src/routes/internal/flows.rs index 1789db693a..f77761b3bc 100644 --- a/apps/labrinth/src/routes/internal/flows.rs +++ b/apps/labrinth/src/routes/internal/flows.rs @@ -2,8 +2,8 @@ use crate::auth::validate::{ get_full_user_from_headers, get_user_record_from_bearer_token, }; use crate::auth::{ - AuthProvider, AuthenticationError, check_account_unlocked, - get_user_from_headers, + AuthProvider, AuthenticationError, check_account_unlocked, + get_user_from_headers, }; use crate::database::PgPool; use crate::database::PgTransaction; @@ -255,7 +255,7 @@ impl TempUser { bio: self.bio, created: Utc::now(), role: Role::Developer.to_string(), - account_locked: false, + account_locked: false, badges: Badges::default(), campaign_pride_26: None, allow_friend_requests: true, @@ -1109,7 +1109,7 @@ pub async fn init( &session_queue, false, ) - .await? + .await? .map(|(_scopes, user)| user.id) } else { None @@ -1292,7 +1292,7 @@ pub async fn auth_callback( "attempting to link a PayPal account without being logged in", )?; - let updated = sqlx::query!( + let updated = sqlx::query!( " UPDATE users SET paypal_country = $1, paypal_email = $2, paypal_id = $3 @@ -1307,9 +1307,9 @@ pub async fn auth_callback( .await .wrap_err("failed to update user PayPal info")?; - if updated.rows_affected() == 0 { - return Err(AuthenticationError::AccountLocked); - } + if updated.rows_affected() == 0 { + return Err(AuthenticationError::AccountLocked); + } transaction .commit() @@ -1332,22 +1332,22 @@ pub async fn auth_callback( return Err(AuthenticationError::ProviderAlreadyLinked); } - let user = DBUser::get_id(id, &**client, &redis) - .await? - .ok_or(AuthenticationError::InvalidCredentials)?; - check_account_unlocked(user.account_locked)?; + let user = DBUser::get_id(id, &**client, &redis) + .await? + .ok_or(AuthenticationError::InvalidCredentials)?; + check_account_unlocked(user.account_locked)?; provider .update_user_id(id, Some(&oauth_user.id), &mut transaction) .await?; - NotificationBuilder { - body: NotificationBody::AuthProviderAdded { - provider: provider.as_str().to_string(), - }, + NotificationBuilder { + body: NotificationBody::AuthProviderAdded { + provider: provider.as_str().to_string(), + }, } - .insert(user.id, &mut transaction, &redis) - .await?; + .insert(user.id, &mut transaction, &redis) + .await?; transaction.commit().await?; crate::database::models::DBUser::clear_caches( @@ -1366,7 +1366,7 @@ pub async fn auth_callback( .await? .ok_or_else(|| AuthenticationError::InvalidCredentials)?; - check_account_unlocked(user.account_locked)?; + check_account_unlocked(user.account_locked)?; if user.totp_secret.is_some() { let flow = DBFlow::Login2FA { user_id: user.id } @@ -2103,7 +2103,7 @@ impl ReadyAccountRegisterFlow { bio: None, created: Utc::now(), role: Role::Developer.to_string(), - account_locked: false, + account_locked: false, badges: Badges::default(), campaign_pride_26: None, allow_friend_requests: true, @@ -2320,8 +2320,8 @@ pub async fn login_password( .map_err(|_| AuthenticationError::InvalidCredentials) .wrap_auth_err("authenticating API request")?; - check_account_unlocked(user.account_locked) - .wrap_auth_err("checking account lock")?; + check_account_unlocked(user.account_locked) + .wrap_auth_err("checking account lock")?; if user.totp_secret.is_some() { let flow = DBFlow::Login2FA { user_id: user.id } @@ -2464,8 +2464,8 @@ pub async fn login_2fa( .ok_or_else(|| AuthenticationError::InvalidCredentials) .wrap_auth_err("fetching user from database")?; - check_account_unlocked(user.account_locked) - .wrap_auth_err("checking account lock")?; + check_account_unlocked(user.account_locked) + .wrap_auth_err("checking account lock")?; let mut transaction = pool .begin() @@ -2918,12 +2918,12 @@ pub async fn reset_password_begin( if let Some(DBUser { id: user_id, email: user_email, - account_locked, + account_locked, .. }) = user { - check_account_unlocked(account_locked) - .wrap_auth_err("checking account lock")?; + check_account_unlocked(account_locked) + .wrap_auth_err("checking account lock")?; let flow = DBFlow::ForgotPassword { user_id } .insert(Duration::hours(24), &redis) @@ -3048,8 +3048,8 @@ pub async fn change_password( user }; - check_account_unlocked(user.account_locked) - .wrap_auth_err("checking account lock")?; + check_account_unlocked(user.account_locked) + .wrap_auth_err("checking account lock")?; let mut transaction = pool .begin() @@ -3422,8 +3422,8 @@ pub async fn verify_email( .ok_or_else(|| AuthenticationError::InvalidCredentials) .wrap_auth_err("fetching user from database")?; - check_account_unlocked(user.account_locked) - .wrap_auth_err("checking account lock")?; + check_account_unlocked(user.account_locked) + .wrap_auth_err("checking account lock")?; if user.email != Some(confirm_email) { return Err(ApiError::Request(eyre::eyre!( @@ -3865,14 +3865,14 @@ pub async fn authenticate_passkey_finish( if let Some(DBFlow::AuthenticatePasskey { state }) = flow { let credential_id = response.credential.get_credential_id(); - let (db_passkey, account_locked) = + let (db_passkey, account_locked) = DBPasskey::get_by_credential_id(credential_id, &**pool) .await .wrap_internal_err("failed to fetch passkey")? .wrap_request_err_with(|| "passkey not found")?; - check_account_unlocked(account_locked) - .wrap_auth_err("checking account lock")?; + check_account_unlocked(account_locked) + .wrap_auth_err("checking account lock")?; let mut transaction = pool .begin() diff --git a/apps/labrinth/src/routes/internal/medal.rs b/apps/labrinth/src/routes/internal/medal.rs index a8f580546c..c8c188886d 100644 --- a/apps/labrinth/src/routes/internal/medal.rs +++ b/apps/labrinth/src/routes/internal/medal.rs @@ -52,14 +52,14 @@ pub async fn verify( match maybe_fields { None => Err(ApiError::NotFound(eyre::eyre!("resource not found"))), - Some(fields) => { - check_account_unlocked(fields.account_locked) - .wrap_auth_err("checking account lock")?; - Ok(HttpResponse::Ok().json(VerifyResponse { - user_id: fields.user_id.into(), - redeemed: fields.redeemal_status.is_some(), - })) - } + Some(fields) => { + check_account_unlocked(fields.account_locked) + .wrap_auth_err("checking account lock")?; + Ok(HttpResponse::Ok().json(VerifyResponse { + user_id: fields.user_id.into(), + redeemed: fields.redeemal_status.is_some(), + })) + } } } @@ -97,8 +97,8 @@ pub async fn redeem( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); } Some(fields) => { - check_account_unlocked(fields.account_locked) - .wrap_auth_err("checking account lock")?; + check_account_unlocked(fields.account_locked) + .wrap_auth_err("checking account lock")?; if fields.redeemal_status.is_some() { return Err(ApiError::Conflict(eyre::eyre!( "User already redeemed this offer", diff --git a/apps/labrinth/src/routes/internal/privacy.rs b/apps/labrinth/src/routes/internal/privacy.rs index 16b6de0e7b..467986c8cc 100644 --- a/apps/labrinth/src/routes/internal/privacy.rs +++ b/apps/labrinth/src/routes/internal/privacy.rs @@ -50,10 +50,10 @@ pub async fn invite_privacy_status( ) .wrap_internal_err("resolving user ids")?; - let user = user.wrap_not_found_err("user not found")?; - check_account_unlocked(user.account_locked) - .wrap_auth_err("checking inviter account lock")?; - let user_id = user.id; + let user = user.wrap_not_found_err("user not found")?; + check_account_unlocked(user.account_locked) + .wrap_auth_err("checking inviter account lock")?; + let user_id = user.id; let target_id = target.wrap_not_found_err("target not found")?.id; let blocked = DBBlockedUser::is_blocked(target_id, user_id, &**pool) diff --git a/apps/labrinth/src/routes/maven.rs b/apps/labrinth/src/routes/maven.rs index a8f0ebff6b..cf7733225b 100644 --- a/apps/labrinth/src/routes/maven.rs +++ b/apps/labrinth/src/routes/maven.rs @@ -95,7 +95,7 @@ pub async fn maven_metadata( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); }; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -103,8 +103,8 @@ pub async fn maven_metadata( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await @@ -333,7 +333,7 @@ pub async fn version_file( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); }; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -341,8 +341,8 @@ pub async fn version_file( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await @@ -420,7 +420,7 @@ pub async fn version_file_sha1( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); }; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -428,8 +428,8 @@ pub async fn version_file_sha1( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await @@ -486,7 +486,7 @@ pub async fn version_file_sha512( return Err(ApiError::NotFound(eyre::eyre!("resource not found"))); }; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -494,8 +494,8 @@ pub async fn version_file_sha512( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await diff --git a/apps/labrinth/src/routes/mod.rs b/apps/labrinth/src/routes/mod.rs index 942cabc4c4..7db046c48b 100644 --- a/apps/labrinth/src/routes/mod.rs +++ b/apps/labrinth/src/routes/mod.rs @@ -276,14 +276,14 @@ impl actix_web::ResponseError for ApiError { match self { Self::Internal(..) => StatusCode::INTERNAL_SERVER_ERROR, Self::Request(..) => StatusCode::BAD_REQUEST, - Self::Auth(report) => { - match report.downcast_ref::() { - Some(AuthenticationError::AccountLocked) => { - StatusCode::FORBIDDEN - } - _ => StatusCode::UNAUTHORIZED, - } - } + Self::Auth(report) => { + match report.downcast_ref::() { + Some(AuthenticationError::AccountLocked) => { + StatusCode::FORBIDDEN + } + _ => StatusCode::UNAUTHORIZED, + } + } Self::NotFound(..) => StatusCode::NOT_FOUND, Self::Conflict(..) => StatusCode::CONFLICT, Self::FailedDependency(..) => StatusCode::FAILED_DEPENDENCY, @@ -303,26 +303,26 @@ mod tests { use super::ApiError; #[test] - fn account_locked_preserves_forbidden_status_through_auth_context() { - use crate::{ - auth::{AuthenticationError, templates::ErrorPage}, - util::error::Context, - }; - use actix_web::{ResponseError, http::StatusCode}; + fn account_locked_preserves_forbidden_status_through_auth_context() { + use crate::{ + auth::{AuthenticationError, templates::ErrorPage}, + util::error::Context, + }; + use actix_web::{ResponseError, http::StatusCode}; - let direct = AuthenticationError::AccountLocked; - assert_eq!(direct.status_code(), StatusCode::FORBIDDEN); - let wrapped = Err::<(), _>(direct) - .wrap_auth_err("checking account lock") - .unwrap_err() - .wrap_err("authenticating API request"); - assert_eq!(wrapped.status_code(), StatusCode::FORBIDDEN); - assert_eq!(wrapped.as_api_error().error, "auth_error"); - let page = ErrorPage::from(AuthenticationError::AccountLocked); - assert_eq!(page.error_response().status(), StatusCode::FORBIDDEN); - } + let direct = AuthenticationError::AccountLocked; + assert_eq!(direct.status_code(), StatusCode::FORBIDDEN); + let wrapped = Err::<(), _>(direct) + .wrap_auth_err("checking account lock") + .unwrap_err() + .wrap_err("authenticating API request"); + assert_eq!(wrapped.status_code(), StatusCode::FORBIDDEN); + assert_eq!(wrapped.as_api_error().error, "auth_error"); + let page = ErrorPage::from(AuthenticationError::AccountLocked); + assert_eq!(page.error_response().status(), StatusCode::FORBIDDEN); + } - #[test] + #[test] fn api_error_serializes_source_chain_as_details() { let error = ApiError::Request( eyre::eyre!("root cause") diff --git a/apps/labrinth/src/routes/updates.rs b/apps/labrinth/src/routes/updates.rs index 41dc2db6ca..cebdc0c3d9 100644 --- a/apps/labrinth/src/routes/updates.rs +++ b/apps/labrinth/src/routes/updates.rs @@ -61,7 +61,7 @@ pub async fn forge_updates( .wrap_api_err("fetching project from database")? .wrap_request_err_with(|| ERROR.to_string())?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -69,8 +69,8 @@ pub async fn forge_updates( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, &user_option, &pool, false) .await diff --git a/apps/labrinth/src/routes/v2/users.rs b/apps/labrinth/src/routes/v2/users.rs index bdc79aaa93..c75cfc45db 100644 --- a/apps/labrinth/src/routes/v2/users.rs +++ b/apps/labrinth/src/routes/v2/users.rs @@ -223,7 +223,7 @@ pub struct EditUser { #[validate(length(max = 160))] pub bio: Option>, pub role: Option, - pub account_locked: Option, + pub account_locked: Option, pub badges: Option, pub allow_friend_requests: Option, } @@ -269,7 +269,7 @@ pub async fn user_edit( username: new_user.username, bio: new_user.bio, role: new_user.role, - account_locked: new_user.account_locked, + account_locked: new_user.account_locked, badges: new_user.badges, venmo_handle: None, allow_friend_requests: new_user.allow_friend_requests, diff --git a/apps/labrinth/src/routes/v3/collections.rs b/apps/labrinth/src/routes/v3/collections.rs index a64dbdaefa..e740222a7b 100644 --- a/apps/labrinth/src/routes/v3/collections.rs +++ b/apps/labrinth/src/routes/v3/collections.rs @@ -1,7 +1,7 @@ use crate::auth::checks::is_visible_collection; use crate::auth::validate::get_maybe_user_from_headers; use crate::auth::{ - filter_visible_collections, get_user_from_headers, require_verified_email, + filter_visible_collections, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; use crate::database::models::{ @@ -174,7 +174,7 @@ pub async fn collections_get( .await .wrap_internal_err("fetching collections from database")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -182,8 +182,8 @@ pub async fn collections_get( Scopes::COLLECTION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let collections = filter_visible_collections(collections_data, &user_option, false) @@ -211,7 +211,7 @@ pub async fn collection_get( database::models::DBCollection::get(id, &**pool, &redis) .await .wrap_internal_err("fetching collection from database")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -219,8 +219,8 @@ pub async fn collection_get( Scopes::COLLECTION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(data) = collection_data && is_visible_collection(&data, &user_option, false) diff --git a/apps/labrinth/src/routes/v3/content/mod.rs b/apps/labrinth/src/routes/v3/content/mod.rs index a857e1f522..33d8827f6e 100644 --- a/apps/labrinth/src/routes/v3/content/mod.rs +++ b/apps/labrinth/src/routes/v3/content/mod.rs @@ -49,7 +49,7 @@ pub async fn resolve_content( redis: web::Data, session_queue: web::Data, ) -> Result, ApiError> { - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -57,8 +57,8 @@ pub async fn resolve_content( Scopes::PROJECT_READ | Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let cache_public_result = user_option.is_none(); let mut provider = LabrinthContentProvider { pool: pool.get_ref(), diff --git a/apps/labrinth/src/routes/v3/disclosures.rs b/apps/labrinth/src/routes/v3/disclosures.rs index ae2afa0f0f..1d4c2e485e 100644 --- a/apps/labrinth/src/routes/v3/disclosures.rs +++ b/apps/labrinth/src/routes/v3/disclosures.rs @@ -54,7 +54,7 @@ pub async fn get_project_disclosures( .wrap_internal_err("failed to fetch project")? .wrap_not_found_err("resource not found")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -62,8 +62,8 @@ pub async fn get_project_disclosures( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|(_, user)| user); + .wrap_auth_err("authenticating API request")? + .map(|(_, user)| user); if !is_visible_project(&project.inner, &user_option, &pool, false) .await diff --git a/apps/labrinth/src/routes/v3/organizations.rs b/apps/labrinth/src/routes/v3/organizations.rs index 710b02bd43..f3280b1033 100644 --- a/apps/labrinth/src/routes/v3/organizations.rs +++ b/apps/labrinth/src/routes/v3/organizations.rs @@ -5,7 +5,7 @@ use std::collections::HashMap; use super::ApiError; use crate::auth::checks::is_visible_organization; use crate::auth::{ - filter_visible_projects, get_user_from_headers, require_verified_email, + filter_visible_projects, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; use crate::database::models::team_item::DBTeamMember; @@ -57,7 +57,7 @@ pub async fn organization_projects_get( session_queue: web::Data, ) -> Result { let id = info.into_inner().0; - let current_user = get_maybe_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -65,8 +65,8 @@ pub async fn organization_projects_get( Scopes::ORGANIZATION_READ | Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let organization_data = DBOrganization::get(&id, &**pool, &redis) .await @@ -239,7 +239,7 @@ pub async fn organization_get( session_queue: web::Data, ) -> Result { let id = info.into_inner().0; - let current_user = get_maybe_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -247,8 +247,8 @@ pub async fn organization_get( Scopes::ORGANIZATION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let user_id = current_user.as_ref().map(|x| x.id.into()); let organization_data = DBOrganization::get(&id, &**pool, &redis) @@ -450,7 +450,7 @@ pub async fn organizations_get( .await .wrap_internal_err("fetching users from database")?; - let current_user = get_maybe_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -458,8 +458,8 @@ pub async fn organizations_get( Scopes::ORGANIZATION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let user_id = current_user.as_ref().map(|x| x.id.into()); let include_notes = current_user.as_ref().is_some_and(|x| x.role.is_mod()); let notes = if include_notes { diff --git a/apps/labrinth/src/routes/v3/project_creation.rs b/apps/labrinth/src/routes/v3/project_creation.rs index 1ab3433051..ecb9967b36 100644 --- a/apps/labrinth/src/routes/v3/project_creation.rs +++ b/apps/labrinth/src/routes/v3/project_creation.rs @@ -1,6 +1,6 @@ use super::version_creation::{InitialVersionData, try_create_version_fields}; use crate::auth::{ - AuthenticationError, get_user_from_headers, require_verified_email, + AuthenticationError, get_user_from_headers, require_verified_email, }; use crate::database::PgPool; use crate::database::PgTransaction; diff --git a/apps/labrinth/src/routes/v3/projects/mod.rs b/apps/labrinth/src/routes/v3/projects/mod.rs index d35b459aa6..24c545119e 100644 --- a/apps/labrinth/src/routes/v3/projects/mod.rs +++ b/apps/labrinth/src/routes/v3/projects/mod.rs @@ -260,7 +260,7 @@ pub async fn projects_get( .await .wrap_api_err("fetching requested projects")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -268,8 +268,8 @@ pub async fn projects_get( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let projects = filter_visible_projects(projects_data, &user_option, &pool, false) @@ -307,7 +307,7 @@ pub async fn project_get_internal( let project_data = db_models::DBProject::get(&string, &**pool, &redis) .await .wrap_internal_err("failed to fetch project")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -315,8 +315,8 @@ pub async fn project_get_internal( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|(_, user)| user); + .wrap_auth_err("authenticating API request")? + .map(|(_, user)| user); if let Some(data) = project_data && is_visible_project(&data.inner, &user_option, &pool, false) @@ -1745,7 +1745,7 @@ pub async fn dependency_list_internal( .await .wrap_api_err("fetching project from database")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -1753,8 +1753,8 @@ pub async fn dependency_list_internal( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(project) = result { if !is_visible_project(&project.inner, &user_option, &pool, false) @@ -3711,7 +3711,7 @@ pub async fn project_get_organization( redis: web::Data, session_queue: web::Data, ) -> Result { - let current_user = get_maybe_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -3719,8 +3719,8 @@ pub async fn project_get_organization( Scopes::PROJECT_READ | Scopes::ORGANIZATION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let user_id = current_user.as_ref().map(|x| x.id.into()); let string = info.into_inner().0; diff --git a/apps/labrinth/src/routes/v3/teams.rs b/apps/labrinth/src/routes/v3/teams.rs index 2b5319b098..78dbcf377b 100644 --- a/apps/labrinth/src/routes/v3/teams.rs +++ b/apps/labrinth/src/routes/v3/teams.rs @@ -67,7 +67,7 @@ pub async fn team_members_get_project_internal( .wrap_api_err("fetching project from database")?; if let Some(project) = project_data { - let current_user = get_maybe_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -75,8 +75,8 @@ pub async fn team_members_get_project_internal( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if !is_visible_project(&project.inner, ¤t_user, &pool, false) .await @@ -156,7 +156,7 @@ pub async fn team_members_get_organization( .await .wrap_internal_err("fetching organization from database")?; - let current_user = get_maybe_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -164,8 +164,8 @@ pub async fn team_members_get_organization( Scopes::ORGANIZATION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(organization) = organization_data && is_visible_organization(&organization, ¤t_user, &pool, &redis) @@ -256,7 +256,7 @@ pub async fn team_members_get( .await .wrap_internal_err("fetching users from database")?; - let current_user = get_maybe_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -264,8 +264,8 @@ pub async fn team_members_get( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let user_id = current_user.as_ref().map(|x| x.id.into()); let logged_in = current_user @@ -347,7 +347,7 @@ pub async fn teams_get( .await .wrap_internal_err("fetching users from database")?; - let current_user = get_maybe_user_from_headers( + let current_user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -355,8 +355,8 @@ pub async fn teams_get( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let teams_groups = teams_data.into_iter().chunk_by(|data| data.team_id.0); diff --git a/apps/labrinth/src/routes/v3/users.rs b/apps/labrinth/src/routes/v3/users.rs index 84ea2bb6fd..5c0562be31 100644 --- a/apps/labrinth/src/routes/v3/users.rs +++ b/apps/labrinth/src/routes/v3/users.rs @@ -16,9 +16,9 @@ use crate::{ filter_visible_collections, filter_visible_projects, get_user_from_headers, }, - database::models::{ - DBModerationNote, DBOrganization, DBProjectId, DBUser, DBUserId, - }, + database::models::{ + DBModerationNote, DBOrganization, DBProjectId, DBUser, DBUserId, + }, file_hosting::{FileHost, FileHostPublicity}, models::{ ids::OrganizationId, @@ -26,7 +26,7 @@ use crate::{ organizations::Organization, pats::Scopes, projects::Project, - users::{Badges, Role, User}, + users::{Badges, Role, User}, }, queue::session::AuthQueue, util::{img::delete_old_images, routes::read_limited_from_payload}, @@ -82,7 +82,7 @@ pub async fn all_projects( redis: web::Data, session_queue: web::Data, ) -> Result, ApiError> { - let user = get_maybe_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -90,8 +90,8 @@ pub async fn all_projects( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let target_user = DBUser::get(&info.into_inner().0, &**pool, &redis) .await .wrap_internal_err("fetching user from database")? @@ -296,7 +296,7 @@ pub async fn projects_list( redis: web::Data, session_queue: web::Data, ) -> Result { - let user = get_maybe_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -304,8 +304,8 @@ pub async fn projects_list( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis) .await @@ -349,7 +349,7 @@ pub async fn user_auth_get( redis: web::Data, session_queue: web::Data, ) -> Result { - let (scopes, db_user) = get_full_user_from_headers_allow_locked( + let (scopes, db_user) = get_full_user_from_headers_allow_locked( &req, &**pool, &redis, @@ -359,14 +359,14 @@ pub async fn user_auth_get( .await .wrap_auth_err("authenticating API request")?; - let mut user = match db_user.account_locked { - false => User::from_full(db_user), - true => { - let mut user = User::from(db_user); - user.account_locked = Some(true); - return Ok(HttpResponse::Ok().json(user)); - } - }; + let mut user = match db_user.account_locked { + false => User::from_full(db_user), + true => { + let mut user = User::from(db_user); + user.account_locked = Some(true); + return Ok(HttpResponse::Ok().json(user)); + } + }; if !scopes.contains(Scopes::USER_READ_EMAIL) { user.email = None; @@ -549,20 +549,20 @@ pub async fn users_get( .await .wrap_internal_err("fetching users from database")?; - let auth_user = get_maybe_user_from_headers( + let auth_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, - Scopes::empty(), + Scopes::empty(), ) .await - .wrap_auth_err("authenticating API request")?; + .wrap_auth_err("authenticating API request")?; - let is_mod = auth_user.as_ref().is_some_and(|(scopes, user)| { - scopes.contains(Scopes::SESSION_ACCESS) && user.role.is_mod() - }); - let notes = if is_mod { + let is_mod = auth_user.as_ref().is_some_and(|(scopes, user)| { + scopes.contains(Scopes::SESSION_ACCESS) && user.role.is_mod() + }); + let notes = if is_mod { DBModerationNote::get_many_users( &users_data.iter().map(|x| x.id).collect::>(), &**pool, @@ -574,21 +574,21 @@ pub async fn users_get( HashMap::new() }; - let users: Vec = users_data + let users: Vec = users_data .into_iter() .map(|data| { - let user_id = data.id; - let visible_account_locked = auth_user - .as_ref() - .filter(|(_, viewer)| { - viewer.id == user_id.into() || viewer.role.is_admin() - }) - .map(|_| data.account_locked); - let mut user = User::from(data); - user.account_locked = visible_account_locked; - if is_mod { + let user_id = data.id; + let visible_account_locked = auth_user + .as_ref() + .filter(|(_, viewer)| { + viewer.id == user_id.into() || viewer.role.is_admin() + }) + .map(|_| data.account_locked); + let mut user = User::from(data); + user.account_locked = visible_account_locked; + if is_mod { user.moderation_notes = - Some(notes.get(&user_id).cloned().map(Into::into)); + Some(notes.get(&user_id).cloned().map(Into::into)); } user }) @@ -621,37 +621,37 @@ pub async fn user_get( .wrap_internal_err("fetching user from database")?; if let Some(data) = user_data { - let auth_user = get_maybe_user_from_headers( + let auth_user = get_maybe_user_from_headers( &req, &**pool, &redis, &session_queue, - Scopes::empty(), + Scopes::empty(), ) .await - .wrap_auth_err("authenticating API request")?; - - let staff_role = auth_user.as_ref().and_then(|(scopes, user)| { - scopes - .contains(Scopes::SESSION_ACCESS) - .then_some(&user.role) - }); - let is_admin = staff_role.is_some_and(Role::is_admin); - let is_mod = staff_role.is_some_and(Role::is_mod); + .wrap_auth_err("authenticating API request")?; + + let staff_role = auth_user.as_ref().and_then(|(scopes, user)| { + scopes + .contains(Scopes::SESSION_ACCESS) + .then_some(&user.role) + }); + let is_admin = staff_role.is_some_and(Role::is_admin); + let is_mod = staff_role.is_some_and(Role::is_mod); let user_id = data.id; - let visible_account_locked = auth_user - .as_ref() - .filter(|(_, viewer)| { - viewer.id == user_id.into() || viewer.role.is_admin() - }) - .map(|_| data.account_locked); - - let mut response = if is_admin { + let visible_account_locked = auth_user + .as_ref() + .filter(|(_, viewer)| { + viewer.id == user_id.into() || viewer.role.is_admin() + }) + .map(|_| data.account_locked); + + let mut response = if is_admin { let github_id = data.github_id.and_then(|id| u64::try_from(id).ok()); let discord_id = data.discord_id.map(|id| id.to_string()); let steam_id = data.steam_id.map(|id| id.to_string()); - let mut user = User::from_full(data); + let mut user = User::from_full(data); user.github_id = github_id; user.discord_id = discord_id; user.steam_id = steam_id; @@ -660,7 +660,7 @@ pub async fn user_get( data.into() }; - response.account_locked = visible_account_locked; + response.account_locked = visible_account_locked; if is_mod { let note = DBModerationNote::get_user(user_id, &**pool, &redis) @@ -770,7 +770,7 @@ pub async fn collections_list( redis: web::Data, session_queue: web::Data, ) -> Result { - let user = get_maybe_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -778,8 +778,8 @@ pub async fn collections_list( Scopes::COLLECTION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis) .await @@ -817,7 +817,7 @@ pub async fn orgs_list( redis: web::Data, session_queue: web::Data, ) -> Result { - let user = get_maybe_user_from_headers( + let user = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -825,8 +825,8 @@ pub async fn orgs_list( Scopes::PROJECT_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let id_option = DBUser::get(&info.into_inner().0, &**pool, &redis) .await @@ -926,7 +926,7 @@ pub struct EditUser { #[validate(length(max = 160))] pub bio: Option>, pub role: Option, - pub account_locked: Option, + pub account_locked: Option, pub badges: Option, #[validate(length(max = 160))] pub venmo_handle: Option, @@ -1025,25 +1025,25 @@ pub async fn user_edit( .execute(&mut transaction) .await .wrap_internal_err("fetching bio from database")?; - } + } - if let Some(account_locked) = new_user.account_locked { - if !user.role.is_admin() { - return Err(ApiError::Auth(eyre::eyre!( - "only admins can edit account lock" - ))); - } + if let Some(account_locked) = new_user.account_locked { + if !user.role.is_admin() { + return Err(ApiError::Auth(eyre::eyre!( + "only admins can edit account lock" + ))); + } - sqlx::query!( - r#" + sqlx::query!( + r#" UPDATE users SET account_locked = $1 WHERE id = $2 "#, - account_locked, - id as DBUserId, - ) - .execute(&mut transaction) - .await - .wrap_internal_err("updating account lock")?; + account_locked, + id as DBUserId, + ) + .execute(&mut transaction) + .await + .wrap_internal_err("updating account lock")?; } if let Some(role) = &new_user.role { diff --git a/apps/labrinth/src/routes/v3/version_file.rs b/apps/labrinth/src/routes/v3/version_file.rs index edd2631dd6..307849e23e 100644 --- a/apps/labrinth/src/routes/v3/version_file.rs +++ b/apps/labrinth/src/routes/v3/version_file.rs @@ -73,7 +73,7 @@ pub async fn get_version_from_hash( hash_query: web::Query, session_queue: web::Data, ) -> Result { - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -81,8 +81,8 @@ pub async fn get_version_from_hash( Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let hash = info.into_inner().0.to_lowercase(); let algorithm = hash_query.algorithm.clone().unwrap_or_else(|| { default_algorithm_from_hashes(std::slice::from_ref(&hash)) @@ -205,7 +205,7 @@ pub async fn get_update_from_hash( update_data: web::Json, session_queue: web::Data, ) -> Result { - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &***pool, &redis, @@ -213,8 +213,8 @@ pub async fn get_update_from_hash( Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let hash = info.into_inner().0.to_lowercase(); if let Some(file) = database::models::DBVersion::get_file_from_hash( hash_query.algorithm.clone().unwrap_or_else(|| { @@ -328,7 +328,7 @@ pub async fn get_versions_from_hashes( file_data: web::Json, session_queue: web::Data, ) -> Result { - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &***pool, &redis, @@ -336,8 +336,8 @@ pub async fn get_versions_from_hashes( Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let algorithm = file_data .algorithm @@ -411,7 +411,7 @@ pub async fn get_projects_from_hashes( file_data: web::Json, session_queue: web::Data, ) -> Result { - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -419,8 +419,8 @@ pub async fn get_projects_from_hashes( Scopes::PROJECT_READ | Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let algorithm = file_data .algorithm @@ -692,7 +692,7 @@ pub async fn update_individual_files( update_data: web::Json, session_queue: web::Data, ) -> Result { - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -700,8 +700,8 @@ pub async fn update_individual_files( Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let algorithm = update_data.algorithm.clone().unwrap_or_else(|| { default_algorithm_from_hashes( @@ -1036,7 +1036,7 @@ pub async fn download_version( hash_query: web::Query, session_queue: web::Data, ) -> Result { - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -1044,8 +1044,8 @@ pub async fn download_version( Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let hash = info.into_inner().0.to_lowercase(); let algorithm = hash_query.algorithm.clone().unwrap_or_else(|| { diff --git a/apps/labrinth/src/routes/v3/versions.rs b/apps/labrinth/src/routes/v3/versions.rs index cfa38b7c15..38c910cc9e 100644 --- a/apps/labrinth/src/routes/v3/versions.rs +++ b/apps/labrinth/src/routes/v3/versions.rs @@ -85,7 +85,7 @@ pub async fn version_project_get_helper( .await .wrap_api_err("fetching project from database")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -93,8 +93,8 @@ pub async fn version_project_get_helper( Scopes::PROJECT_READ | Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(project) = result { if !is_visible_project(&project.inner, &user_option, &pool, false) @@ -212,7 +212,7 @@ pub async fn versions_get( .await .wrap_internal_err("fetching versions from database")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -220,8 +220,8 @@ pub async fn versions_get( Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); let mut versions = filter_visible_versions( versions_data, @@ -294,7 +294,7 @@ pub async fn version_get_helper( .await .wrap_internal_err("fetching version from database")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -302,8 +302,8 @@ pub async fn version_get_helper( Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(data) = version_data && is_visible_version(&data.inner, &user_option, &pool, &redis) @@ -1028,7 +1028,7 @@ pub async fn version_list_internal( .await .wrap_api_err("fetching project from database")?; - let user_option = get_maybe_user_from_headers( + let user_option = get_maybe_user_from_headers( &req, &**pool, &redis, @@ -1036,8 +1036,8 @@ pub async fn version_list_internal( Scopes::PROJECT_READ | Scopes::VERSION_READ, ) .await - .wrap_auth_err("authenticating API request")? - .map(|x| x.1); + .wrap_auth_err("authenticating API request")? + .map(|x| x.1); if let Some(project) = result { if !is_visible_project(&project.inner, &user_option, &pool, false) diff --git a/apps/labrinth/tests/account_locked.rs b/apps/labrinth/tests/account_locked.rs index d476d2045d..d818fbf3a8 100644 --- a/apps/labrinth/tests/account_locked.rs +++ b/apps/labrinth/tests/account_locked.rs @@ -4,56 +4,56 @@ use actix_http::StatusCode; use actix_web::{http::Method, test}; use chrono::{Duration, Utc}; use common::{ - api_common::AppendsOptionalPat, - api_v3::{ - ApiV3, - oauth::{ - get_auth_code_from_redirect_params, get_authorize_accept_flow_id, - }, - }, - database::{ADMIN_USER_PAT, FRIEND_USER_PAT, MOD_USER_PAT, USER_USER_PAT}, - environment::{TestEnvironment, with_test_environment}, + api_common::AppendsOptionalPat, + api_v3::{ + ApiV3, + oauth::{ + get_auth_code_from_redirect_params, get_authorize_accept_flow_id, + }, + }, + database::{ADMIN_USER_PAT, FRIEND_USER_PAT, MOD_USER_PAT, USER_USER_PAT}, + environment::{TestEnvironment, with_test_environment}, }; use labrinth::{ - auth::{ - AuthenticationError, get_user_from_headers, - validate::{ - get_full_user_from_headers_allow_locked, - get_maybe_user_from_headers, get_user_record_from_bearer_token, - }, - }, - database::models::{ - DBUserId, flow_item::DBFlow, session_item::SessionBuilder, - }, - env::ENV, - models::pats::Scopes, - queue::session::AuthQueue, + auth::{ + AuthenticationError, get_user_from_headers, + validate::{ + get_full_user_from_headers_allow_locked, + get_maybe_user_from_headers, get_user_record_from_bearer_token, + }, + }, + database::models::{ + DBUserId, flow_item::DBFlow, session_item::SessionBuilder, + }, + env::ENV, + models::pats::Scopes, + queue::session::AuthQueue, }; use serde_json::{Value, json}; pub mod common; async fn set_account_locked( - env: &TestEnvironment, - user: &str, - account_locked: bool, + env: &TestEnvironment, + user: &str, + account_locked: bool, ) { - let response = env - .call( - test::TestRequest::patch() - .uri(&format!("/v3/user/{user}")) - .append_pat(ADMIN_USER_PAT) - .set_json(json!({"account_locked": account_locked})) - .to_request(), - ) - .await; - assert_status!(&response, StatusCode::NO_CONTENT); + let response = env + .call( + test::TestRequest::patch() + .uri(&format!("/v3/user/{user}")) + .append_pat(ADMIN_USER_PAT) + .set_json(json!({"account_locked": account_locked})) + .to_request(), + ) + .await; + assert_status!(&response, StatusCode::NO_CONTENT); } #[actix_rt::test] async fn account_locked_is_visible_only_to_self_and_admin_in_both_api_versions() { - with_test_environment(None, |env: TestEnvironment| async move { + with_test_environment(None, |env: TestEnvironment| async move { let mut restricted_tokens = Vec::new(); for pat in [USER_USER_PAT, ADMIN_USER_PAT] { let response = env @@ -207,147 +207,147 @@ async fn account_locked_is_visible_only_to_self_and_admin_in_both_api_versions() #[actix_rt::test] async fn existing_pat_session_and_oauth_credentials_obey_account_locks_and_cache_invalidation() { - with_test_environment(None, |env: TestEnvironment| async move { - let client = &env.dummy.oauth_client_alpha; - let oauth_token = env - .api - .complete_full_authorize_flow( - &client.client_id, - &client.client_secret, - Some("USER_READ USER_WRITE"), - None, - None, - USER_USER_PAT, - ) - .await; - let mut transaction = env.db.pool.begin().await.unwrap(); - SessionBuilder { - session: "mra_account_locked_test".into(), - user_id: DBUserId(3), - os: None, - platform: None, - city: None, - country: None, - ip: "127.0.0.1".into(), - user_agent: "account lock test".into(), - expires: None, - session_expires: None, - } - .insert(&mut transaction) - .await - .unwrap(); - transaction.commit().await.unwrap(); - let tokens = [ - USER_USER_PAT.unwrap(), - "mra_account_locked_test", - oauth_token.as_str(), - ]; - let queue = AuthQueue::new(); - for account_locked in [false, true, false] { - set_account_locked(&env, "3", account_locked).await; - for token in tokens { - let req = test::TestRequest::get() - .append_pat(Some(token)) - .append_header(( - "x-ratelimit-key", - ENV.RATE_LIMIT_IGNORE_KEY.as_str(), - )) - .to_http_request(); - let result = get_user_from_headers( - &req, - &*env.db.pool, - &env.db.redis_pool, - &queue, - Scopes::USER_READ, - ) - .await; - match account_locked { - true => assert!(matches!( - result, - Err(AuthenticationError::AccountLocked) - )), - false => assert_eq!( - result.unwrap().1.account_locked, - Some(false) - ), - } - let (_, user) = get_full_user_from_headers_allow_locked( - &req, - &*env.db.pool, - &env.db.redis_pool, - &queue, - Scopes::USER_READ, - ) - .await - .unwrap(); - assert_eq!(user.account_locked, account_locked); - let response = env - .call( - test::TestRequest::get() - .uri("/v3/user") - .append_pat(Some(token)) - .append_header(( - "x-ratelimit-key", - ENV.RATE_LIMIT_IGNORE_KEY.as_str(), - )) - .to_request(), - ) - .await; - assert_status!(&response, StatusCode::OK); - let body: Value = test::read_body_json(response).await; - assert_eq!(body["account_locked"], json!(account_locked)); - if account_locked { - assert!(body["auth_providers"].is_null()); - assert!(body["email"].is_null()); - assert!(matches!( - get_maybe_user_from_headers( - &req, - &*env.db.pool, - &env.db.redis_pool, - &queue, - Scopes::SESSION_ACCESS, - ) - .await, - Err(AuthenticationError::AccountLocked) - )); - assert!(matches!( - get_user_record_from_bearer_token( - &req, - Some(token), - &*env.db.pool, - &env.db.redis_pool, - &queue, - true, - ) - .await, - Err(AuthenticationError::AccountLocked) - )); - } - } - } - let req = test::TestRequest::get() - .append_pat(Some(&oauth_token)) - .to_http_request(); - let result = get_full_user_from_headers_allow_locked( - &req, - &*env.db.pool, - &env.db.redis_pool, - &queue, - Scopes::USER_AUTH_WRITE, - ) - .await; - assert!(matches!( - result, - Err(AuthenticationError::InvalidCredentials) - )); - }) - .await; + with_test_environment(None, |env: TestEnvironment| async move { + let client = &env.dummy.oauth_client_alpha; + let oauth_token = env + .api + .complete_full_authorize_flow( + &client.client_id, + &client.client_secret, + Some("USER_READ USER_WRITE"), + None, + None, + USER_USER_PAT, + ) + .await; + let mut transaction = env.db.pool.begin().await.unwrap(); + SessionBuilder { + session: "mra_account_locked_test".into(), + user_id: DBUserId(3), + os: None, + platform: None, + city: None, + country: None, + ip: "127.0.0.1".into(), + user_agent: "account lock test".into(), + expires: None, + session_expires: None, + } + .insert(&mut transaction) + .await + .unwrap(); + transaction.commit().await.unwrap(); + let tokens = [ + USER_USER_PAT.unwrap(), + "mra_account_locked_test", + oauth_token.as_str(), + ]; + let queue = AuthQueue::new(); + for account_locked in [false, true, false] { + set_account_locked(&env, "3", account_locked).await; + for token in tokens { + let req = test::TestRequest::get() + .append_pat(Some(token)) + .append_header(( + "x-ratelimit-key", + ENV.RATE_LIMIT_IGNORE_KEY.as_str(), + )) + .to_http_request(); + let result = get_user_from_headers( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::USER_READ, + ) + .await; + match account_locked { + true => assert!(matches!( + result, + Err(AuthenticationError::AccountLocked) + )), + false => assert_eq!( + result.unwrap().1.account_locked, + Some(false) + ), + } + let (_, user) = get_full_user_from_headers_allow_locked( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::USER_READ, + ) + .await + .unwrap(); + assert_eq!(user.account_locked, account_locked); + let response = env + .call( + test::TestRequest::get() + .uri("/v3/user") + .append_pat(Some(token)) + .append_header(( + "x-ratelimit-key", + ENV.RATE_LIMIT_IGNORE_KEY.as_str(), + )) + .to_request(), + ) + .await; + assert_status!(&response, StatusCode::OK); + let body: Value = test::read_body_json(response).await; + assert_eq!(body["account_locked"], json!(account_locked)); + if account_locked { + assert!(body["auth_providers"].is_null()); + assert!(body["email"].is_null()); + assert!(matches!( + get_maybe_user_from_headers( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::SESSION_ACCESS, + ) + .await, + Err(AuthenticationError::AccountLocked) + )); + assert!(matches!( + get_user_record_from_bearer_token( + &req, + Some(token), + &*env.db.pool, + &env.db.redis_pool, + &queue, + true, + ) + .await, + Err(AuthenticationError::AccountLocked) + )); + } + } + } + let req = test::TestRequest::get() + .append_pat(Some(&oauth_token)) + .to_http_request(); + let result = get_full_user_from_headers_allow_locked( + &req, + &*env.db.pool, + &env.db.redis_pool, + &queue, + Scopes::USER_AUTH_WRITE, + ) + .await; + assert!(matches!( + result, + Err(AuthenticationError::InvalidCredentials) + )); + }) + .await; } #[actix_rt::test] async fn locked_accounts_cannot_mutate_resources_read_sensitive_data_or_unlock_themselves() { - with_test_environment(None, |env: TestEnvironment| async move { + with_test_environment(None, |env: TestEnvironment| async move { for pat in [USER_USER_PAT, MOD_USER_PAT] { let response = env.call(test::TestRequest::patch().uri("/v3/user/3") .append_pat(pat).set_json(json!({"account_locked": true})).to_request()).await; @@ -405,7 +405,7 @@ async fn locked_accounts_cannot_mutate_resources_read_sensitive_data_or_unlock_t #[actix_rt::test] async fn flows_created_before_lock_cannot_reset_password_verify_email_or_issue_tokens() { - with_test_environment(None, |env: TestEnvironment| async move { + with_test_environment(None, |env: TestEnvironment| async move { let password_flow = DBFlow::ForgotPassword { user_id: DBUserId(3) }.insert(Duration::hours(1), &env.db.redis_pool).await.unwrap(); let email_flow = DBFlow::ConfirmEmail { user_id: DBUserId(3), confirm_email: "user@modrinth.com".into() }.insert(Duration::hours(1), &env.db.redis_pool).await.unwrap(); let login_flow = DBFlow::Login2FA { user_id: DBUserId(3) }.insert(Duration::hours(1), &env.db.redis_pool).await.unwrap();