diff --git a/crates/tranquil-pds/tests/did_web.rs b/crates/tranquil-pds/tests/did_web.rs index d3e0286..03edad2 100644 --- a/crates/tranquil-pds/tests/did_web.rs +++ b/crates/tranquil-pds/tests/did_web.rs @@ -177,7 +177,10 @@ async fn test_external_did_web_no_local_doc() { async fn reserve_signing_key(client: &reqwest::Client, base: &str, did: &str) -> String { let res = client - .post(format!("{}/xrpc/com.atproto.server.reserveSigningKey", base)) + .post(format!( + "{}/xrpc/com.atproto.server.reserveSigningKey", + base + )) .json(&json!({ "did": did })) .send() .await diff --git a/crates/tranquil-pds/tests/oauth_lifecycle.rs b/crates/tranquil-pds/tests/oauth_lifecycle.rs index 92c987b..4f10ad5 100644 --- a/crates/tranquil-pds/tests/oauth_lifecycle.rs +++ b/crates/tranquil-pds/tests/oauth_lifecycle.rs @@ -605,6 +605,7 @@ async fn test_oauth_multiple_clients_same_user() { ("redirect_uri", "https://client1.example.com/callback"), ("code_challenge", &challenge1), ("code_challenge_method", "S256"), + ("scope", "atproto repo:app.bsky.feed.post?action=create"), ]) .send() .await @@ -631,7 +632,7 @@ async fn test_oauth_multiple_clients_same_user() { let consent_res = http_client .post(format!("{}/oauth/authorize/consent", url)) .header("Content-Type", "application/json") - .json(&json!({"request_uri": request_uri1, "approved_scopes": ["atproto", "transition:generic"], "remember": false})) + .json(&json!({"request_uri": request_uri1, "approved_scopes": ["atproto", "repo:app.bsky.feed.post?action=create"], "remember": false})) .send().await.unwrap(); let consent_body: Value = consent_res.json().await.unwrap(); location1 = consent_body["redirect_uri"].as_str().unwrap().to_string(); @@ -666,6 +667,7 @@ async fn test_oauth_multiple_clients_same_user() { ("redirect_uri", "https://client2.example.com/callback"), ("code_challenge", &challenge2), ("code_challenge_method", "S256"), + ("scope", "atproto repo:app.bsky.feed.post?action=create"), ]) .send() .await @@ -692,7 +694,7 @@ async fn test_oauth_multiple_clients_same_user() { let consent_res = http_client .post(format!("{}/oauth/authorize/consent", url)) .header("Content-Type", "application/json") - .json(&json!({"request_uri": request_uri2, "approved_scopes": ["atproto", "transition:generic"], "remember": false})) + .json(&json!({"request_uri": request_uri2, "approved_scopes": ["atproto", "repo:app.bsky.feed.post?action=create"], "remember": false})) .send().await.unwrap(); let consent_body: Value = consent_res.json().await.unwrap(); location2 = consent_body["redirect_uri"].as_str().unwrap().to_string(); @@ -763,6 +765,33 @@ async fn test_oauth_multiple_clients_same_user() { StatusCode::OK, "Client 2 token should work" ); + let ungranted_collection = "app.bsky.graph.follow"; + let denied_res = http_client + .post(format!("{}/xrpc/com.atproto.repo.createRecord", url)) + .bearer_auth(token1) + .json(&json!({ + "repo": user_did, + "collection": ungranted_collection, + "record": { + "$type": ungranted_collection, + "subject": user_did, + "createdAt": Utc::now().to_rfc3339() + } + })) + .send() + .await + .unwrap(); + assert_eq!( + denied_res.status(), + StatusCode::FORBIDDEN, + "Client 1 token shouldn't write outside its granted collection" + ); + let denied_body: Value = denied_res.json().await.unwrap(); + assert_eq!( + denied_body["error"].as_str(), + Some("InsufficientScope"), + "Write outside the granted collection should fail on scope" + ); let list_res = http_client .get(format!("{}/xrpc/com.atproto.repo.listRecords", url)) .bearer_auth(token1) @@ -770,6 +799,7 @@ async fn test_oauth_multiple_clients_same_user() { .send() .await .unwrap(); + assert_eq!(list_res.status(), StatusCode::OK, "listRecords should work"); let list_body: Value = list_res.json().await.unwrap(); let records = list_body["records"].as_array().unwrap(); assert_eq!( diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json index c9c7b8a..fe26323 100644 --- a/frontend/src/locales/en.json +++ b/frontend/src/locales/en.json @@ -609,7 +609,8 @@ "malformed": "This bundle's name is not a valid identifier, so it cannot be granted.", "not_a_permission_set": "This identifier does not refer to a permission bundle, so it cannot be granted.", "malformed_lexicon": "This bundle is published incorrectly and cannot be granted.", - "empty_permissions": "This bundle does not currently grant any permissions." + "empty_permissions": "This bundle does not currently grant any permissions.", + "unknown": "This bundle cannot be granted." }, "permTable": { "data": "Data", diff --git a/frontend/src/locales/fi.json b/frontend/src/locales/fi.json index 804a961..e8cbbf5 100644 --- a/frontend/src/locales/fi.json +++ b/frontend/src/locales/fi.json @@ -603,6 +603,29 @@ "permissionsRequested": "Pyydetyt oikeudet", "required": "Vaaditaan", "rememberChoiceLabel": "Muista valintani tälle sovellukselle", + "permissionSets": "Oikeuspaketit", + "showIncludedScopes": "Näytä sisältyvät oikeudet ({count})", + "unavailablePermissions": "Ei käytettävissä olevat oikeudet", + "unavailableLimited": "Delegoinnin rajoittama", + "unavailableFailed": "Lataus epäonnistui", + "setPartiallyLimited": "Delegointisi rajoittaa osaa tämän paketin oikeuksista. Kohta \"Ei käytettävissä olevat oikeudet\" kertoo, mitä ei sallittu.", + "setFailureReason": { + "not_found": "Pakettia ei löytynyt, joten sitä ei voi myöntää.", + "unreachable": "Paketin julkaisijaan ei saatu yhteyttä, joten sitä ei voi nyt myöntää.", + "malformed": "Paketin nimi ei ole kelvollinen tunniste, joten sitä ei voi myöntää.", + "not_a_permission_set": "Tunniste ei viittaa oikeuspakettiin, joten sitä ei voi myöntää.", + "malformed_lexicon": "Paketti on julkaistu virheellisesti, joten sitä ei voi myöntää.", + "empty_permissions": "Paketti ei myönnä tällä hetkellä yhtään oikeutta.", + "unknown": "Pakettia ei voi myöntää." + }, + "permTable": { + "data": "Tiedot", + "create": "Luonti", + "update": "Päivitys", + "delete": "Poisto", + "allData": "Kaikki kokoelmat", + "apiAccess": "API-käyttö" + }, "scopes": { "atproto": { "name": "AT Protocol -käyttöoikeus", diff --git a/frontend/src/locales/fr.json b/frontend/src/locales/fr.json index dc64c8f..8a7ce1b 100644 --- a/frontend/src/locales/fr.json +++ b/frontend/src/locales/fr.json @@ -597,6 +597,29 @@ "permissionsRequested": "Permissions demandées", "required": "Requis", "rememberChoiceLabel": "Se souvenir de mon choix pour cette application", + "permissionSets": "Ensembles de permissions", + "showIncludedScopes": "Afficher les permissions incluses ({count})", + "unavailablePermissions": "Permissions indisponibles", + "unavailableLimited": "Limité par la délégation", + "unavailableFailed": "Échec du chargement", + "setPartiallyLimited": "Votre délégation limite certaines permissions de cet ensemble. La section « Permissions indisponibles » indique lesquelles ont été refusées.", + "setFailureReason": { + "not_found": "Cet ensemble est introuvable et ne peut pas être accordé.", + "unreachable": "L'éditeur de cet ensemble est injoignable, il ne peut donc pas être accordé pour le moment.", + "malformed": "Le nom de cet ensemble n'est pas un identifiant valide, il ne peut donc pas être accordé.", + "not_a_permission_set": "Cet identifiant ne désigne pas un ensemble de permissions, il ne peut donc pas être accordé.", + "malformed_lexicon": "Cet ensemble est publié de façon incorrecte et ne peut pas être accordé.", + "empty_permissions": "Cet ensemble n'accorde actuellement aucune permission.", + "unknown": "Cet ensemble ne peut pas être accordé." + }, + "permTable": { + "data": "Données", + "create": "Créer", + "update": "Modifier", + "delete": "Supprimer", + "allData": "Toutes les collections", + "apiAccess": "Accès API" + }, "scopes": { "atproto": { "name": "Accès AT Protocol", diff --git a/frontend/src/locales/ja.json b/frontend/src/locales/ja.json index f0422cc..c136074 100644 --- a/frontend/src/locales/ja.json +++ b/frontend/src/locales/ja.json @@ -603,6 +603,29 @@ "permissionsRequested": "リクエストされた権限", "required": "必須", "rememberChoiceLabel": "このアプリに対する選択を記憶する", + "permissionSets": "権限バンドル", + "showIncludedScopes": "含まれる権限を表示 ({count})", + "unavailablePermissions": "利用できない権限", + "unavailableLimited": "委任による制限", + "unavailableFailed": "読み込み失敗", + "setPartiallyLimited": "このバンドルの一部の権限は委任設定により制限されています。許可されなかった権限は「利用できない権限」で確認できます。", + "setFailureReason": { + "not_found": "このバンドルは見つからないため、付与できません。", + "unreachable": "バンドルの公開元に接続できないため、現在は付与できません。", + "malformed": "このバンドルの名前が有効な識別子ではないため、付与できません。", + "not_a_permission_set": "この識別子は権限バンドルを指していないため、付与できません。", + "malformed_lexicon": "このバンドルは正しく公開されていないため、付与できません。", + "empty_permissions": "このバンドルは現在、権限を付与しません。", + "unknown": "このバンドルは付与できません。" + }, + "permTable": { + "data": "データ", + "create": "作成", + "update": "更新", + "delete": "削除", + "allData": "すべてのコレクション", + "apiAccess": "API アクセス" + }, "scopes": { "atproto": { "name": "AT Protocol アクセス", diff --git a/frontend/src/locales/ko.json b/frontend/src/locales/ko.json index d57cb87..dc7d211 100644 --- a/frontend/src/locales/ko.json +++ b/frontend/src/locales/ko.json @@ -603,6 +603,29 @@ "permissionsRequested": "요청된 권한", "required": "필수", "rememberChoiceLabel": "이 앱에 대한 선택 기억하기", + "permissionSets": "권한 번들", + "showIncludedScopes": "포함된 권한 보기 ({count})", + "unavailablePermissions": "사용할 수 없는 권한", + "unavailableLimited": "위임으로 제한됨", + "unavailableFailed": "불러오기 실패", + "setPartiallyLimited": "위임 설정이 이 번들의 일부 권한을 제한합니다. 허용되지 않은 권한은 \"사용할 수 없는 권한\" 항목에서 확인하세요.", + "setFailureReason": { + "not_found": "이 번들을 찾을 수 없어 부여할 수 없습니다.", + "unreachable": "번들 게시자에 연결할 수 없어 지금은 부여할 수 없습니다.", + "malformed": "이 번들의 이름이 유효한 식별자가 아니어서 부여할 수 없습니다.", + "not_a_permission_set": "이 식별자는 권한 번들을 가리키지 않아 부여할 수 없습니다.", + "malformed_lexicon": "이 번들이 잘못 게시되어 부여할 수 없습니다.", + "empty_permissions": "이 번들은 현재 어떤 권한도 부여하지 않습니다.", + "unknown": "이 번들은 부여할 수 없습니다." + }, + "permTable": { + "data": "데이터", + "create": "생성", + "update": "수정", + "delete": "삭제", + "allData": "모든 컬렉션", + "apiAccess": "API 접근" + }, "scopes": { "atproto": { "name": "AT Protocol 액세스", diff --git a/frontend/src/locales/sv.json b/frontend/src/locales/sv.json index 1a11c96..3cb6cc3 100644 --- a/frontend/src/locales/sv.json +++ b/frontend/src/locales/sv.json @@ -603,6 +603,29 @@ "permissionsRequested": "Begärda behörigheter", "required": "Krävs", "rememberChoiceLabel": "Kom ihåg mitt val för denna applikation", + "permissionSets": "Behörighetspaket", + "showIncludedScopes": "Visa inkluderade behörigheter ({count})", + "unavailablePermissions": "Otillgängliga behörigheter", + "unavailableLimited": "Begränsad av delegering", + "unavailableFailed": "Kunde inte laddas", + "setPartiallyLimited": "Din delegering begränsar vissa behörigheter i paketet. Avsnittet \"Otillgängliga behörigheter\" visar vilka som nekades.", + "setFailureReason": { + "not_found": "Paketet hittades inte och kan inte beviljas.", + "unreachable": "Paketets utgivare kunde inte nås, så paketet kan inte beviljas just nu.", + "malformed": "Paketets namn är inte en giltig identifierare, så det kan inte beviljas.", + "not_a_permission_set": "Identifieraren avser inte ett behörighetspaket, så det kan inte beviljas.", + "malformed_lexicon": "Paketet är felaktigt publicerat och kan inte beviljas.", + "empty_permissions": "Paketet beviljar för närvarande inga behörigheter.", + "unknown": "Paketet kan inte beviljas." + }, + "permTable": { + "data": "Data", + "create": "Skapa", + "update": "Uppdatera", + "delete": "Radera", + "allData": "Alla samlingar", + "apiAccess": "API-åtkomst" + }, "scopes": { "atproto": { "name": "AT Protocol-åtkomst", diff --git a/frontend/src/locales/zh.json b/frontend/src/locales/zh.json index 246ee6a..3750c70 100644 --- a/frontend/src/locales/zh.json +++ b/frontend/src/locales/zh.json @@ -603,6 +603,29 @@ "permissionsRequested": "请求的权限", "required": "必需", "rememberChoiceLabel": "记住对此应用的授权选择", + "permissionSets": "权限包", + "showIncludedScopes": "显示包含的权限 ({count})", + "unavailablePermissions": "不可用权限", + "unavailableLimited": "受委托限制", + "unavailableFailed": "加载失败", + "setPartiallyLimited": "您的委托设置限制了此权限包中的部分权限。“不可用权限”一节列出了未被允许的权限。", + "setFailureReason": { + "not_found": "找不到此权限包,无法授予。", + "unreachable": "无法连接此权限包的发布方,目前无法授予。", + "malformed": "此权限包的名称不是有效标识符,无法授予。", + "not_a_permission_set": "此标识符并非指向权限包,无法授予。", + "malformed_lexicon": "此权限包发布有误,无法授予。", + "empty_permissions": "此权限包目前不授予任何权限。", + "unknown": "此权限包无法授予。" + }, + "permTable": { + "data": "数据", + "create": "创建", + "update": "更新", + "delete": "删除", + "allData": "所有集合", + "apiAccess": "API 访问" + }, "scopes": { "atproto": { "name": "AT Protocol 访问", diff --git a/frontend/src/routes/OAuthConsent.svelte b/frontend/src/routes/OAuthConsent.svelte index e77ce97..30693cd 100644 --- a/frontend/src/routes/OAuthConsent.svelte +++ b/frontend/src/routes/OAuthConsent.svelte @@ -44,6 +44,28 @@ restricted?: boolean } + type SetFailureReason = + | 'unreachable' + | 'not_found' + | 'malformed' + | 'not_a_permission_set' + | 'malformed_lexicon' + | 'empty_permissions' + + const SET_FAILURE_LOCALE_KEYS: Record = { + unreachable: 'oauth.consent.setFailureReason.unreachable', + not_found: 'oauth.consent.setFailureReason.not_found', + malformed: 'oauth.consent.setFailureReason.malformed', + not_a_permission_set: 'oauth.consent.setFailureReason.not_a_permission_set', + malformed_lexicon: 'oauth.consent.setFailureReason.malformed_lexicon', + empty_permissions: 'oauth.consent.setFailureReason.empty_permissions', + } + + function setFailureLocaleKey(reason: string): string { + const known: Partial> = SET_FAILURE_LOCALE_KEYS + return known[reason] ?? 'oauth.consent.setFailureReason.unknown' + } + interface FailedSetInfo { nsid: string aud?: string @@ -572,7 +594,7 @@
{f.nsid}{#if f.aud} ({f.aud}){/if} - {$_(`oauth.consent.setFailureReason.${f.reason}`)} + {$_(setFailureLocaleKey(f.reason))}
{/each}