주소 권한 부여와 회수
두 번째 축: 한 사람이 어떤 주소에, 어느 수준으로 접근할 수 있는지.
이 페이지의 2개 호출을 본인 키로 워크스페이스에 실제로 실행합니다.
POST /members/{userId}/addresses
두 번째 축: 한 사람이 어떤 주소에, 어느 수준으로 접근할 수 있는지.
권한 부여는 역할이 아니다
access는 주소 단위의 예전 어휘이며, 권한 이름과 겹치지 않도록 일부러 다르게 지었습니다. member는 주소를 읽고 그 주소로 보내며, viewer는 읽기만 합니다. 이 값은 그 사람이 애초에 보낼 수 있는지에 대해서는 아무 말도 하지 않으며, 그것은 역할이 정합니다. 발송이 이루어지려면 둘 다 허용해야 합니다.
| 역할 | billing@에 대한 권한 | billing@으로 보낼 수 있는가 |
|---|---|---|
| `emails:send` 보유 | member | 예. |
| `emails:send` 보유 | viewer | 아니요. 권한 부여가 거부합니다. |
| `emails:send` 없음 | member | 아니요. 역할이 거부합니다. |
| `emails:send` 보유 | 권한 부여 없음 | 아니요. 그 주소는 발송 시 검사하는 목록에 아예 들어가지 않습니다. |
누군가에게 역할을 준다고 주소가 주어지지는 않습니다. 역할만 있고 권한이 없는 멤버는 모두의 메일함이 아니라 빈 메일함을 엽니다. 무엇을 보여줄지 아직 정하는 중이라면 그것이 올바른 실패입니다.
주소 권한 부여
members:write가 필요합니다. POST /members/{userId}/addresses에 { addressId, access }를 보냅니다. access의 기본값은 member입니다. 현재 상태의 멤버 전체를 반환합니다.
curl -X POST "$OE/members/nQ8vBz1aRd4tYwKx7fQ2mN8vBz1aRd4t/addresses" -H "$AUTH" \ -H "Content-Type: application/json" \ -d '{ "addressId": "c40a95f2-1cc6-4d31-82a8-9e075d31c2a8", "access": "viewer" }'{ "object": "member", "userId": "nQ8vBz1aRd4tYwKx7fQ2mN8vBz1aRd4t", "email": "[email protected]", "name": "Sam Okonjo", "image": null, "role": { "id": "role_2b81de079c1f0a4b7e05d386", "name": "Support", "builtin": null }, "implied": false, "permissions": ["emails:send", "emails:read", "threads:read", "threads:write"], "addresses": [ { "addressId": "2b81de07-9c1f-4a4b-8e05-d3862c1f0a44", "address": "[email protected]", "access": "member" }, { "addressId": "c40a95f2-1cc6-4d31-82a8-9e075d31c2a8", "address": "[email protected]", "access": "viewer" } ], "createdAt": "2026-08-12T14:20:00.000Z" }쌍에 대한 PUT이 아니라 하위 컬렉션에 대한 POST인 이유는, 어느 쪽이든 upsert이고 행 id는 호출자가 지정할 일이 없기 때문입니다. 다른 access로 다시 POST하는 것이 viewer를 member로 바꾸는 방법입니다. (주소, 사람) 쌍당 행이 하나뿐이므로, 두 번째 호출은 권한을 하나 더 추가하지 않고 수준을 바꿉니다. 그래서 이것은 드물게도 반복해도 안전한 POST입니다.
주소 소유 여부가 아니라 members:write로 통제하며, 이것이 도메인 라우터에 있는 예전 권한 부여 경로와의 차이입니다. 소유권은 도메인을 등록한 사람에게는 맞는 기준이지만, 아무것도 소유하지 않은 채 소유자를 대신해 워크스페이스의 접근 권한을 운영하는 관리자에게는 틀린 기준입니다. 둘은 같은 행을 씁니다.
권한 하나가 아니라 멤버 전체가 돌아오므로, 화면의 행을 두 번째 요청 없이 다시 그릴 수 있고, 권한이 새로 생겼든 수정됐든 응답이 똑같이 읽힙니다.
이 워크스페이스에 없는 주소는 member_not_found, 422이며 param: "addressId"를 담습니다. 키는 자신이 발급된 워크스페이스에 속한 주소만 나눠줄 수 있습니다.
워크스페이스 소유자에게 부여하려 하면 member_is_owner, 422입니다. 소유자는 이미 그 워크스페이스의 모든 주소를 갖고 있으므로 이 호출이 더할 것이 없습니다.
주소 권한 회수
members:write가 필요합니다. DELETE /members/{userId}/addresses/{addressId}. 해당 주소가 빠진 멤버를 반환합니다.
curl -X DELETE \ "$OE/members/nQ8vBz1aRd4tYwKx7fQ2mN8vBz1aRd4t/addresses/c40a95f2-1cc6-4d31-82a8-9e075d31c2a8" \ -H "$AUTH"{ "object": "member", "userId": "nQ8vBz1aRd4tYwKx7fQ2mN8vBz1aRd4t", "email": "[email protected]", "name": "Sam Okonjo", "image": null, "role": { "id": "role_2b81de079c1f0a4b7e05d386", "name": "Support", "builtin": null }, "implied": false, "permissions": ["emails:send", "emails:read", "threads:read", "threads:write"], "addresses": [ { "addressId": "2b81de07-9c1f-4a4b-8e05-d3862c1f0a44", "address": "[email protected]", "access": "member" } ], "createdAt": "2026-08-12T14:20:00.000Z" }좁은 범위의 회수이며, 누군가 팀을 옮겼을 때 손이 가야 할 호출입니다. 역할과 다른 주소는 그대로 두고 이 주소만 보이지 않게 됩니다.
이 워크스페이스에 없는 주소는 조용히 무시되지 않고 거부됩니다. 그렇지 않으면 id 오타가 실제로는 일어나지 않은 회수를 성공했다고 보고하게 되는데, 이 엔드포인트는 바로 그 실패를 막기 위해 존재합니다.
비석이 아니라 멤버가 돌아오는 이유는, 정작 궁금한 답이 그 사람이 여전히 접근할 수 있는 범위이기 때문입니다. 여기서 { deleted: true }를 주면 클라이언트가 뺄셈으로 알아내야 합니다.
한 번에 전부 회수하려면 DELETE /members/{userId}가 역할과 모든 권한을 함께 제거하고 몇 개가 사라졌는지 보고합니다.