diff --git a/README.md b/README.md index cfcd436..a33a5bd 100644 --- a/README.md +++ b/README.md @@ -83,7 +83,7 @@ The API layer includes reusable authentication and account endpoints alongside g | ------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | Auth | `/auth/register`, `/auth/login`, `/auth/otp-request`, `/auth/forgot-password`, `/auth/refresh-token`, `/auth/change-password`, `/auth/logout` | | Account | `/account/me`, `/account/update-profile`, `/account/update-username`, `/account/request-email-verification`, `/account/verify-email` | -| Users | `/users`, `/users/:id`, `/users/:id/roles`, `/users/:id/workspaces`, `/users/:id/password`, `/users/:id/status`, `/users/:id/system-admin` | +| Users | `/users`, `/users/:id`, `/users/:id/roles`, `/users/:id/password`, `/users/:id/status`, `/users/:id/system-admin` | | Roles | `/roles`, `/roles/:id`, `/permissions` | Protected API requests automatically make one refresh attempt after a `401`. diff --git a/collection/README.md b/collection/README.md new file mode 100644 index 0000000..dcf03f0 --- /dev/null +++ b/collection/README.md @@ -0,0 +1,36 @@ +# Kernel API — Bruno Collection + +This collection contains only the API operations used by the Kernel frontend, +along with the saved responses available for those operations. + +## Open the collection + +1. Install and open Bruno. +2. Choose **Open Collection**. +3. Select this `collection` directory. +4. Select the **development** environment. + +## Included requests + +- Authentication: register, login, OTP request, refresh token, forgot password, + change password, and logout. +- Account: current account, profile and username updates, and email verification. +- Roles: list, create, details, update, and delete. +- Permissions: list. +- Users: list, create, details, update, delete, status, system-admin, password, + and roles. + +The frontend refreshes an expired access token through `/auth/refresh-token`, +so that request is included even though it is handled by the shared API client +rather than a feature page. + +## First run + +Run the login request with a local test account. Its post-response script stores +the returned access token as `access_token` in the active environment. The +remaining authenticated requests use that token. + +Saved response files sit beside each request and are named after their HTTP +status code. + +Do not store real passwords or access tokens in this shared collection. diff --git a/collection/account/folder.bru b/collection/account/folder.bru new file mode 100644 index 0000000..bb3f7c0 --- /dev/null +++ b/collection/account/folder.bru @@ -0,0 +1,4 @@ +meta { + name: Account + seq: 1 +} diff --git a/collection/account/me/200.json b/collection/account/me/200.json new file mode 100644 index 0000000..5001fdd --- /dev/null +++ b/collection/account/me/200.json @@ -0,0 +1,12 @@ +{ + "email": "user@example.com", + "first_name": "علی", + "id": "2dc56d6f-6b5a-4e0d-9b0b-9a1c9d7f8d41", + "is_system_admin": true, + "last_name": "رضایی", + "mobile": "09123456789", + "permissions": ["users.read", "users.create", "users.update"], + "personnel_code": "EMP-1001", + "status": "active", + "username": "user" +} diff --git a/collection/account/me/401.json b/collection/account/me/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/account/me/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/account/me/index.bru b/collection/account/me/index.bru new file mode 100644 index 0000000..c0cc9c5 --- /dev/null +++ b/collection/account/me/index.bru @@ -0,0 +1,42 @@ +meta { + name: Me + type: http + seq: 1 +} + +get { + url: {{base_url}}/account/me + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +docs { + ## Purpose + + Returns the profile of the currently authenticated user. + + ## Authentication + + Requires a valid access token. + + ## Behavior + + - Returns the authenticated user's profile, including `personnel_code` and `is_system_admin`. + - `is_system_admin` indicates whether the user has the global `SYSTEM_ADMIN` capability. + - An authenticated `SYSTEM_ADMIN` can use every service without assigned role permissions. + - No per-user language preference is returned; the system language applies to every user. + - No request body or query parameters are required. + + ## Response + + - `200 OK` — User profile returned successfully. + - `401 Unauthorized` — The access token is missing, invalid, or expired. +} diff --git a/collection/account/request-email-verification/200.json b/collection/account/request-email-verification/200.json new file mode 100644 index 0000000..d0b9eb4 --- /dev/null +++ b/collection/account/request-email-verification/200.json @@ -0,0 +1,4 @@ +{ + "expires_in": 120, + "remaining_seconds": 120 +} diff --git a/collection/account/request-email-verification/400.json b/collection/account/request-email-verification/400.json new file mode 100644 index 0000000..06c96db --- /dev/null +++ b/collection/account/request-email-verification/400.json @@ -0,0 +1,6 @@ +{ + "cause": { + "email": "آدرس ایمیل معتبر نیست." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/account/request-email-verification/401.json b/collection/account/request-email-verification/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/account/request-email-verification/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/account/request-email-verification/429.json b/collection/account/request-email-verification/429.json new file mode 100644 index 0000000..73878aa --- /dev/null +++ b/collection/account/request-email-verification/429.json @@ -0,0 +1,3 @@ +{ + "message": "تعداد درخواست‌ها بیش از حد مجاز است. لطفاً کمی بعد دوباره تلاش کنید." +} diff --git a/collection/account/request-email-verification/index.bru b/collection/account/request-email-verification/index.bru new file mode 100644 index 0000000..2c31614 --- /dev/null +++ b/collection/account/request-email-verification/index.bru @@ -0,0 +1,50 @@ +meta { + name: Request Email Verification + type: http + seq: 4 +} + +post { + url: {{base_url}}/account/request-email-verification + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "email": "user@example.com" + } +} + +docs { + ## Purpose + + Sends a verification code to an email address before assigning it to the authenticated user. + + ## Authentication + + Requires a valid access token. + + ## Behavior + + - Sends a verification code to the provided email address. + - The current profile email remains unchanged until verification succeeds. + - The same flow is used for both setting an email for the first time and changing an existing email. + - Repeated requests may be rate limited. + + ## Response + + - `200 OK` — Verification code sent successfully. + - `400 Bad Request` — The email is invalid or cannot be used. + - `401 Unauthorized` — The access token is missing, invalid, or expired. + - `429 Too Many Requests` — The request rate limit has been exceeded. +} diff --git a/collection/account/update-profile/200.json b/collection/account/update-profile/200.json new file mode 100644 index 0000000..5001fdd --- /dev/null +++ b/collection/account/update-profile/200.json @@ -0,0 +1,12 @@ +{ + "email": "user@example.com", + "first_name": "علی", + "id": "2dc56d6f-6b5a-4e0d-9b0b-9a1c9d7f8d41", + "is_system_admin": true, + "last_name": "رضایی", + "mobile": "09123456789", + "permissions": ["users.read", "users.create", "users.update"], + "personnel_code": "EMP-1001", + "status": "active", + "username": "user" +} diff --git a/collection/account/update-profile/400.json b/collection/account/update-profile/400.json new file mode 100644 index 0000000..98322e6 --- /dev/null +++ b/collection/account/update-profile/400.json @@ -0,0 +1,8 @@ +{ + "cause": { + "first_name": "نام معتبر نیست.", + "last_name": "نام خانوادگی معتبر نیست.", + "personnel_code": "کد پرسنلی معتبر نیست یا قبلاً ثبت شده است." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/account/update-profile/401.json b/collection/account/update-profile/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/account/update-profile/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/account/update-profile/index.bru b/collection/account/update-profile/index.bru new file mode 100644 index 0000000..7ee3514 --- /dev/null +++ b/collection/account/update-profile/index.bru @@ -0,0 +1,55 @@ +meta { + name: Update Profile + type: http + seq: 2 +} + +patch { + url: {{base_url}}/account/update-profile + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "first_name": "علی", + "last_name": "رضایی", + "personnel_code": "EMP-1001" + } +} + +docs { + ## Purpose + + Partially updates the basic profile information of the authenticated user. + + ## Authentication + + Requires a valid access token. + + ## Behavior + + - Only fields included in the request body are updated. + - Omitted fields remain unchanged. + - At least one supported field must be provided. + - Supported fields are `first_name`, `last_name`, and `personnel_code`. + - `personnel_code` must remain unique when changed. + - Users do not have individual language preferences. + - Username and email are managed through their dedicated endpoints. + - Avatar management is not included until the file system contract is defined. + + ## Response + + - `200 OK` — Profile updated successfully. + - `400 Bad Request` — The request body is empty or contains invalid values. + - `401 Unauthorized` — The access token is missing, invalid, or expired. +} diff --git a/collection/account/update-username/200.json b/collection/account/update-username/200.json new file mode 100644 index 0000000..5001fdd --- /dev/null +++ b/collection/account/update-username/200.json @@ -0,0 +1,12 @@ +{ + "email": "user@example.com", + "first_name": "علی", + "id": "2dc56d6f-6b5a-4e0d-9b0b-9a1c9d7f8d41", + "is_system_admin": true, + "last_name": "رضایی", + "mobile": "09123456789", + "permissions": ["users.read", "users.create", "users.update"], + "personnel_code": "EMP-1001", + "status": "active", + "username": "user" +} diff --git a/collection/account/update-username/400.json b/collection/account/update-username/400.json new file mode 100644 index 0000000..ef74648 --- /dev/null +++ b/collection/account/update-username/400.json @@ -0,0 +1,6 @@ +{ + "cause": { + "username": "این نام کاربری قبلاً استفاده شده است." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/account/update-username/401.json b/collection/account/update-username/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/account/update-username/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/account/update-username/index.bru b/collection/account/update-username/index.bru new file mode 100644 index 0000000..112dcfe --- /dev/null +++ b/collection/account/update-username/index.bru @@ -0,0 +1,47 @@ +meta { + name: Update Username + type: http + seq: 3 +} + +post { + url: {{base_url}}/account/update-username + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "username": "user" + } +} + +docs { + ## Purpose + + Updates the username of the authenticated user. + + ## Authentication + + Requires a valid access token. + + ## Behavior + + - Updates the username of the authenticated user. + - The username must be unique. + + ## Response + + - `200 OK` — Username updated successfully. + - `400 Bad Request` — The username is invalid or already in use. + - `401 Unauthorized` — The access token is missing, invalid, or expired. +} diff --git a/collection/account/verify-email/400.json b/collection/account/verify-email/400.json new file mode 100644 index 0000000..1dbff92 --- /dev/null +++ b/collection/account/verify-email/400.json @@ -0,0 +1,7 @@ +{ + "cause": { + "email": "آدرس ایمیل معتبر نیست.", + "otp": "کد تأیید معتبر نیست یا منقضی شده است." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/account/verify-email/401.json b/collection/account/verify-email/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/account/verify-email/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/account/verify-email/index.bru b/collection/account/verify-email/index.bru new file mode 100644 index 0000000..c284d6a --- /dev/null +++ b/collection/account/verify-email/index.bru @@ -0,0 +1,52 @@ +meta { + name: Verify Email + type: http + seq: 5 +} + +post { + url: {{base_url}}/account/verify-email + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "email": "user@example.com", + "otp": "123456" + } +} + +docs { + ## Purpose + + Verifies an email address and assigns it to the authenticated user. + + ## Authentication + + Requires a valid access token. + + ## Behavior + + - Verifies the code previously sent to the provided email address. + - The email must match the address used in the verification request. + - If the user has no email, the verified email is added to the profile. + - If the user already has an email, it is replaced only after successful verification. + - The existing profile email remains unchanged when verification fails. + - The email is stored only after verification succeeds. + + ## Response + + - `200 OK` — Email verified and assigned successfully with no response body. + - `400 Bad Request` — The request is invalid or the verification code is invalid or expired. + - `401 Unauthorized` — The access token is missing, invalid, or expired. +} diff --git a/collection/auth/change-password/400.json b/collection/auth/change-password/400.json new file mode 100644 index 0000000..80aba52 --- /dev/null +++ b/collection/auth/change-password/400.json @@ -0,0 +1,7 @@ +{ + "cause": { + "current_password": "رمز عبور فعلی الزامی است.", + "new_password": "رمز عبور جدید الزامی است." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/auth/change-password/401.json b/collection/auth/change-password/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/auth/change-password/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/auth/change-password/index.bru b/collection/auth/change-password/index.bru new file mode 100644 index 0000000..e52c4af --- /dev/null +++ b/collection/auth/change-password/index.bru @@ -0,0 +1,51 @@ +meta { + name: Change Password + type: http + seq: 6 +} + +post { + url: {{base_url}}/auth/change-password + body: json + auth: bearer +} + +headers { + Content-Type: application/json + Accept: application/json +} + +auth:bearer { + token: {{access_token}} +} + +body:json { + { + "current_password": "CurrentPassword123!", + "new_password": "NewPassword123!" + } +} + +docs { + ## Purpose + + Changes the password of the authenticated user. + + ## Authentication + + Requires a valid access token. + + ## Behavior + + - The current password must be valid. + - The new password must satisfy the configured password policy. + - The new password must be different from the current password. + - This endpoint is intended for users who already have a password. + - A successful request returns no response body. + + ## Response + + - `200 OK` — Password changed successfully with no response body. + - `400 Bad Request` — Request validation failed, the current password is incorrect, or the new password is invalid. + - `401 Unauthorized` — Authentication is missing or invalid. +} diff --git a/collection/auth/folder.bru b/collection/auth/folder.bru new file mode 100644 index 0000000..9c7e98d --- /dev/null +++ b/collection/auth/folder.bru @@ -0,0 +1,4 @@ +meta { + name: Authentication + seq: 4 +} diff --git a/collection/auth/forgot-password/400.json b/collection/auth/forgot-password/400.json new file mode 100644 index 0000000..17281f7 --- /dev/null +++ b/collection/auth/forgot-password/400.json @@ -0,0 +1,8 @@ +{ + "cause": { + "mobile": "شماره موبایل واردشده معتبر نیست.", + "otp": "کد تأیید معتبر نیست یا منقضی شده است.", + "password": "رمز عبور شرایط لازم را ندارد." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/auth/forgot-password/index.bru b/collection/auth/forgot-password/index.bru new file mode 100644 index 0000000..4216528 --- /dev/null +++ b/collection/auth/forgot-password/index.bru @@ -0,0 +1,50 @@ +meta { + name: Forgot Password + type: http + seq: 5 +} + +post { + url: {{base_url}}/auth/forgot-password + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "mobile": "09123456789", + "otp": "123456", + "password": "NewPassword123!" + } +} + +docs { + ## Purpose + + Resets the password of a user who cannot access their account with the existing password. + + ## Authentication + + Public endpoint. Identity is verified using the OTP included in the request. + + ## Behavior + + - An OTP must first be requested using the OTP Request endpoint with the `forgot_password` purpose. + - The mobile number must belong to an existing user. + - The OTP must be valid, unexpired, and issued for the same mobile number and purpose. + - The new password must satisfy the configured password policy. + - The OTP becomes unusable after a successful password reset. + - The current password is not required. + - A successful password reset does not create an authenticated session. + - The user must sign in using the new password. + + ## Response + + - `200 OK` — Password reset successfully with no response body. + - `400 Bad Request` — Request validation failed, the OTP is invalid or expired, the mobile number is invalid, or the password does not satisfy the password policy. +} diff --git a/collection/auth/login/200.json b/collection/auth/login/200.json new file mode 100644 index 0000000..2631422 --- /dev/null +++ b/collection/auth/login/200.json @@ -0,0 +1,3 @@ +{ + "access_token": "" +} diff --git a/collection/auth/login/400.json b/collection/auth/login/400.json new file mode 100644 index 0000000..482ba30 --- /dev/null +++ b/collection/auth/login/400.json @@ -0,0 +1,7 @@ +{ + "cause": { + "identifier": "شماره موبایل الزامی است.", + "password": "رمز عبور الزامی است." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/auth/login/index.bru b/collection/auth/login/index.bru new file mode 100644 index 0000000..0e15356 --- /dev/null +++ b/collection/auth/login/index.bru @@ -0,0 +1,60 @@ +meta { + name: Login + type: http + seq: 1 +} + +post { + url: {{base_url}}/auth/login + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "identifier": "09123456789", + "password": "password123" + } +} + +script:post-response { + const responseBody = res.getBody(); + const payload = responseBody?.data ?? responseBody; + + if (res.getStatus() === 200 && payload?.access_token) { + bru.setEnvVar("access_token", payload.access_token); + } else { + bru.deleteEnvVar("access_token"); + } +} + +docs { + ## Purpose + + Authenticates a user using an identifier and password. + + ## Authentication + + Public endpoint. + + ## Behavior + + - The identifier is validated as a mobile number in this project. + - The backend setting `AUTH_IDENTIFIER_TYPE=mobile` defines this validation rule. + - The supplied credentials must be valid. + - Password authentication must be enabled for the account. + - A successful request returns an access token. + - The issued access token has `principal_type: "user"`. + - The refresh token is issued through the configured HttpOnly cookie. + - The response must not reveal whether the identifier or password was incorrect. + + ## Response + + - `200 OK` — Authentication successful. + - `400 Bad Request` — Request validation failed or the credentials are invalid. +} diff --git a/collection/auth/logout/401.json b/collection/auth/logout/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/auth/logout/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/auth/logout/index.bru b/collection/auth/logout/index.bru new file mode 100644 index 0000000..9cd7fd5 --- /dev/null +++ b/collection/auth/logout/index.bru @@ -0,0 +1,47 @@ +meta { + name: Logout + type: http + seq: 7 +} + +post { + url: {{base_url}}/auth/logout + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +script:post-response { + if (res.getStatus() === 200) { + bru.deleteEnvVar("access_token"); + } +} + +docs { + ## Purpose + + Signs out the authenticated user and revokes the current session. + + ## Authentication + + Requires a valid access token. + + ## Behavior + + - Invalidates the current authenticated session. + - Revokes the associated refresh session. + - No request body is required. + - This endpoint is idempotent. Calling it multiple times produces the same result. + + ## Response + + - `200 OK` — Logout successful with no response body. + - `401 Unauthorized` — Authentication is missing or invalid. +} diff --git a/collection/auth/otp-request/200.json b/collection/auth/otp-request/200.json new file mode 100644 index 0000000..d0b9eb4 --- /dev/null +++ b/collection/auth/otp-request/200.json @@ -0,0 +1,4 @@ +{ + "expires_in": 120, + "remaining_seconds": 120 +} diff --git a/collection/auth/otp-request/400.json b/collection/auth/otp-request/400.json new file mode 100644 index 0000000..0aedc94 --- /dev/null +++ b/collection/auth/otp-request/400.json @@ -0,0 +1,6 @@ +{ + "cause": { + "mobile": "شماره موبایل واردشده معتبر نیست." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/auth/otp-request/429.json b/collection/auth/otp-request/429.json new file mode 100644 index 0000000..73878aa --- /dev/null +++ b/collection/auth/otp-request/429.json @@ -0,0 +1,3 @@ +{ + "message": "تعداد درخواست‌ها بیش از حد مجاز است. لطفاً کمی بعد دوباره تلاش کنید." +} diff --git a/collection/auth/otp-request/index.bru b/collection/auth/otp-request/index.bru new file mode 100644 index 0000000..80d12c3 --- /dev/null +++ b/collection/auth/otp-request/index.bru @@ -0,0 +1,54 @@ +meta { + name: OTP Request + type: http + seq: 3 +} + +post { + url: {{base_url}}/auth/otp-request + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "mobile": "09123456789", + "purpose": "login" + } +} + +docs { + ## Purpose + + Requests a one-time password (OTP) for a supported authentication flow. + + ## Authentication + + Public endpoint. + + ## Behavior + + - Generates and sends an OTP for the specified mobile number and purpose. + - Supported purposes: + - `login` + - `register` + - `forgot_password` + - `verify_email` + - The mobile number must be in the supported format. + - Repeated OTP requests may progressively increase the resend delay. + - The client must always use the `remaining_seconds` value returned by the server and must not assume a fixed resend interval. + - OTP requests are rate-limited based on the mobile number and request IP address. + - Repeated requests may temporarily block further OTP delivery. + - The response does not reveal whether the mobile number belongs to an existing account. + + ## Response + + - `200 OK` — OTP request accepted. + - `400 Bad Request` — Request validation failed or the purpose is invalid. + - `429 Too Many Requests` — OTP request rate limit exceeded. +} diff --git a/collection/auth/refresh-token/200.json b/collection/auth/refresh-token/200.json new file mode 100644 index 0000000..c238338 --- /dev/null +++ b/collection/auth/refresh-token/200.json @@ -0,0 +1,3 @@ +{ + "access_token": "" +} diff --git a/collection/auth/refresh-token/401.json b/collection/auth/refresh-token/401.json new file mode 100644 index 0000000..5acf481 --- /dev/null +++ b/collection/auth/refresh-token/401.json @@ -0,0 +1,3 @@ +{ + "message": "نشست شما منقضی شده است. لطفاً دوباره وارد شوید." +} diff --git a/collection/auth/refresh-token/index.bru b/collection/auth/refresh-token/index.bru new file mode 100644 index 0000000..b1d9628 --- /dev/null +++ b/collection/auth/refresh-token/index.bru @@ -0,0 +1,47 @@ +meta { + name: Refresh Token + type: http + seq: 4 +} + +post { + url: {{base_url}}/auth/refresh-token + body: none + auth: none +} + +headers { + Accept: application/json +} + +script:post-response { + const responseBody = res.getBody(); + const payload = responseBody?.data ?? responseBody; + + if (payload?.access_token) { + bru.setEnvVar("access_token", payload.access_token); + } +} + +docs { + ## Purpose + + Issues a new access token using the refresh token cookie. + + ## Authentication + + Requires a valid refresh token cookie. + + ## Behavior + + - No request body is required. + - The refresh token is read from the cookie. + - A successful request issues a new access token. + - The refreshed access token preserves `principal_type: "user"`. + - The returned access token is stored as `access_token` in the Bruno environment. + + ## Response + + - `200 OK` — Access token refreshed. + - `401 Unauthorized` — Refresh token is missing, invalid, or expired. +} diff --git a/collection/auth/register/201.json b/collection/auth/register/201.json new file mode 100644 index 0000000..2631422 --- /dev/null +++ b/collection/auth/register/201.json @@ -0,0 +1,3 @@ +{ + "access_token": "" +} diff --git a/collection/auth/register/400.json b/collection/auth/register/400.json new file mode 100644 index 0000000..c972897 --- /dev/null +++ b/collection/auth/register/400.json @@ -0,0 +1,10 @@ +{ + "cause": { + "first_name": "نام الزامی است.", + "last_name": "نام خانوادگی الزامی است.", + "mobile": "این شماره موبایل قبلاً ثبت شده است.", + "otp": "کد تأیید معتبر نیست یا منقضی شده است.", + "password": "رمز عبور الزامی است و باید شرایط لازم را داشته باشد." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/auth/register/index.bru b/collection/auth/register/index.bru new file mode 100644 index 0000000..09357a4 --- /dev/null +++ b/collection/auth/register/index.bru @@ -0,0 +1,63 @@ +meta { + name: Register + type: http + seq: 2 +} + +post { + url: {{base_url}}/auth/register + body: json + auth: none +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "first_name": "علی", + "last_name": "رضایی", + "mobile": "09123456789", + "otp": "123456", + "password": "password123" + } +} + +script:post-response { + const responseBody = res.getBody(); + const payload = responseBody?.data ?? responseBody; + + if (res.getStatus() === 201 && payload?.access_token) { + bru.setEnvVar("access_token", payload.access_token); + } else { + bru.deleteEnvVar("access_token"); + } +} + +docs { + ## Purpose + + Creates a new account after verifying the registration OTP. + + ## Authentication + + Public endpoint. + + ## Behavior + + - A valid OTP requested with the `register` purpose is required. + - The issued access token has `principal_type: "user"`. + - The mobile number must not already belong to an existing account. + - The user's basic profile is created using the provided first and last name. + - Password is required and must satisfy the configured password policy. + - A successful registration authenticates the newly created user. + - The access token is returned in the response. + - The refresh token is issued through the configured HttpOnly cookie. + + ## Response + + - `201 Created` — Account created and authentication completed successfully. + - `400 Bad Request` — Request validation failed, the mobile number is already registered, the OTP is invalid, or the password does not satisfy the password policy. +} diff --git a/collection/bruno.json b/collection/bruno.json new file mode 100644 index 0000000..91d4c80 --- /dev/null +++ b/collection/bruno.json @@ -0,0 +1,6 @@ +{ + "ignore": [".agents", "node_modules", ".git"], + "name": "Kernel API", + "type": "collection", + "version": "1" +} diff --git a/collection/collection.bru b/collection/collection.bru new file mode 100644 index 0000000..54b262f --- /dev/null +++ b/collection/collection.bru @@ -0,0 +1,11 @@ +auth { + mode: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} diff --git a/collection/environments/development.bru b/collection/environments/development.bru new file mode 100644 index 0000000..397cf70 --- /dev/null +++ b/collection/environments/development.bru @@ -0,0 +1,6 @@ +vars { + access_token: + base_url: http://localhost:3000 + role_id: 3ecb1f52-6d6e-43b9-a8fb-4e0772c9f863 + user_id: 2dc56d6f-6b5a-4e0d-9b0b-9a1c9d7f8d41 +} diff --git a/collection/permissions/folder.bru b/collection/permissions/folder.bru new file mode 100644 index 0000000..529dda0 --- /dev/null +++ b/collection/permissions/folder.bru @@ -0,0 +1,4 @@ +meta { + name: Permissions + seq: 10 +} diff --git a/collection/permissions/list/200.json b/collection/permissions/list/200.json new file mode 100644 index 0000000..76335fd --- /dev/null +++ b/collection/permissions/list/200.json @@ -0,0 +1,248 @@ +[ + { + "name": "ai_module_options", + "permissions": [ + { + "name": "ai_module_options.create", + "title": "ایجاد گزینه ماژول هوش مصنوعی" + }, + { + "name": "ai_module_options.delete", + "title": "حذف گزینه ماژول هوش مصنوعی" + }, + { + "name": "ai_module_options.read", + "title": "مشاهده گزینه‌های ماژول هوش مصنوعی" + }, + { + "name": "ai_module_options.update", + "title": "ویرایش گزینه ماژول هوش مصنوعی" + } + ], + "title": "گزینه‌های ماژول هوش مصنوعی" + }, + { + "name": "ai_modules", + "permissions": [ + { + "name": "ai_modules.create", + "title": "ایجاد ماژول هوش مصنوعی" + }, + { + "name": "ai_modules.delete", + "title": "حذف ماژول هوش مصنوعی" + }, + { + "name": "ai_modules.read", + "title": "مشاهده ماژول‌های هوش مصنوعی" + }, + { + "name": "ai_modules.update", + "title": "ویرایش ماژول هوش مصنوعی" + } + ], + "title": "ماژول‌های هوش مصنوعی" + }, + { + "name": "clients", + "permissions": [ + { + "name": "clients.read", + "title": "مشاهده مشتریان" + } + ], + "title": "مشتریان" + }, + { + "name": "holidays", + "permissions": [ + { + "name": "holidays.create", + "title": "ایجاد تعطیلی" + }, + { + "name": "holidays.delete", + "title": "حذف تعطیلی" + }, + { + "name": "holidays.read", + "title": "مشاهده تعطیلات" + } + ], + "title": "تعطیلات" + }, + { + "name": "identity_verifications", + "permissions": [ + { + "name": "identity_verifications.read", + "title": "مشاهده درخواست‌های احراز هویت" + }, + { + "name": "identity_verifications.update", + "title": "بررسی درخواست‌های احراز هویت" + } + ], + "title": "احراز هویت" + }, + { + "name": "languages", + "permissions": [ + { + "name": "languages.read", + "title": "مشاهده زبان‌ها" + }, + { + "name": "languages.update", + "title": "ویرایش زبان سیستم" + } + ], + "title": "زبان‌ها" + }, + { + "name": "permissions", + "permissions": [ + { + "name": "permissions.read", + "title": "مشاهده دسترسی‌ها" + } + ], + "title": "دسترسی‌ها" + }, + { + "name": "policies", + "permissions": [ + { + "name": "policies.create", + "title": "ایجاد سیاست" + }, + { + "name": "policies.read", + "title": "مشاهده سیاست‌ها" + } + ], + "title": "سیاست‌ها" + }, + { + "name": "rejection_reasons", + "permissions": [ + { + "name": "rejection_reasons.create", + "title": "ایجاد دلیل رد" + }, + { + "name": "rejection_reasons.delete", + "title": "حذف دلیل رد" + }, + { + "name": "rejection_reasons.read", + "title": "مشاهده دلایل رد" + }, + { + "name": "rejection_reasons.update", + "title": "ویرایش دلیل رد" + } + ], + "title": "دلایل رد" + }, + { + "name": "reports", + "permissions": [ + { + "name": "reports.read", + "title": "مشاهده گزارش‌ها" + } + ], + "title": "گزارش‌ها" + }, + { + "name": "roles", + "permissions": [ + { + "name": "roles.create", + "title": "ایجاد نقش" + }, + { + "name": "roles.delete", + "title": "حذف نقش" + }, + { + "name": "roles.read", + "title": "مشاهده نقش‌ها" + }, + { + "name": "roles.update", + "title": "ویرایش نقش" + } + ], + "title": "نقش‌ها" + }, + { + "name": "services", + "permissions": [ + { + "name": "services.read", + "title": "مشاهده خدمات" + }, + { + "name": "services.update", + "title": "ویرایش خدمات" + } + ], + "title": "خدمات" + }, + { + "name": "sessions", + "permissions": [ + { + "name": "sessions.export", + "title": "خروجی نشست‌ها" + }, + { + "name": "sessions.read", + "title": "مشاهده نشست‌ها" + }, + { + "name": "sessions.update", + "title": "مدیریت نشست‌ها" + } + ], + "title": "نشست‌ها" + }, + { + "name": "settings", + "permissions": [ + { + "name": "settings.read", + "title": "مشاهده تنظیمات" + }, + { + "name": "settings.update", + "title": "ویرایش تنظیمات" + } + ], + "title": "تنظیمات" + }, + { + "name": "users", + "permissions": [ + { + "name": "users.create", + "title": "ایجاد کاربر" + }, + { + "name": "users.delete", + "title": "حذف کاربر" + }, + { + "name": "users.read", + "title": "مشاهده کاربران" + }, + { + "name": "users.update", + "title": "ویرایش کاربر" + } + ], + "title": "کاربران" + } +] diff --git a/collection/permissions/list/401.json b/collection/permissions/list/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/permissions/list/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/permissions/list/403.json b/collection/permissions/list/403.json new file mode 100644 index 0000000..960bae4 --- /dev/null +++ b/collection/permissions/list/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه مشاهده سطوح دسترسی را ندارید." +} diff --git a/collection/permissions/list/index.bru b/collection/permissions/list/index.bru new file mode 100644 index 0000000..33c3cb1 --- /dev/null +++ b/collection/permissions/list/index.bru @@ -0,0 +1,44 @@ +meta { + name: List Permissions + type: http + seq: 1 +} + +get { + url: {{base_url}}/permissions + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +docs { + ## Purpose + + Returns all permissions available for assignment to roles. + + ## Authentication + + Requires a valid access token with permission to read permissions. + + ## Behavior + + - Returns permissions grouped by resource. + - Each group contains `name`, `title`, and a `permissions` array. + - Each nested permission contains its assignable full `name` and display-ready `title`. + - Roles continue to store permission name values such as `users.read`. + - `SYSTEM_ADMIN` is a global capability and is not included in this assignable permission catalog. + - An authenticated `SYSTEM_ADMIN` can use every service without assigned role permissions. + + ## Response + + - `200 OK` — Permissions returned successfully. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot read permissions. +} diff --git a/collection/roles/create/201.json b/collection/roles/create/201.json new file mode 100644 index 0000000..cf950df --- /dev/null +++ b/collection/roles/create/201.json @@ -0,0 +1,5 @@ +{ + "id": "3ecb1f52-6d6e-43b9-a8fb-4e0772c9f863", + "name": "کاربر", + "permissions": ["users.read"] +} diff --git a/collection/roles/create/400.json b/collection/roles/create/400.json new file mode 100644 index 0000000..e233ae6 --- /dev/null +++ b/collection/roles/create/400.json @@ -0,0 +1,7 @@ +{ + "cause": { + "name": "نام نقش الزامی است یا قبلاً استفاده شده است.", + "permissions": "فهرست دسترسی‌ها معتبر نیست." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/roles/create/401.json b/collection/roles/create/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/roles/create/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/roles/create/403.json b/collection/roles/create/403.json new file mode 100644 index 0000000..e677c29 --- /dev/null +++ b/collection/roles/create/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه ایجاد نقش را ندارید." +} diff --git a/collection/roles/create/index.bru b/collection/roles/create/index.bru new file mode 100644 index 0000000..fc68b1c --- /dev/null +++ b/collection/roles/create/index.bru @@ -0,0 +1,50 @@ +meta { + name: Create Role + type: http + seq: 2 +} + +post { + url: {{base_url}}/roles + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json + Content-Type: application/json +} + +body:json { + { + "name": "کاربر", + "permissions": ["users.read"] + } +} + +docs { + ## Purpose + + Creates a role. + + ## Authentication + + Requires a valid access token with permission to create roles. + + ## Behavior + + - Role names must be unique. + - The server generates the role UUID. + - Permissions is an array of names returned by GET /permissions and may be empty. + + ## Response + + - `201 Created` — Role created successfully. + - `400 Bad Request` — The role name or permissions are invalid. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot create roles. +} diff --git a/collection/roles/delete/401.json b/collection/roles/delete/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/roles/delete/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/roles/delete/403.json b/collection/roles/delete/403.json new file mode 100644 index 0000000..4a150ea --- /dev/null +++ b/collection/roles/delete/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه حذف این نقش را ندارید." +} diff --git a/collection/roles/delete/404.json b/collection/roles/delete/404.json new file mode 100644 index 0000000..e6dccb8 --- /dev/null +++ b/collection/roles/delete/404.json @@ -0,0 +1,3 @@ +{ + "message": "نقش موردنظر یافت نشد." +} diff --git a/collection/roles/delete/409.json b/collection/roles/delete/409.json new file mode 100644 index 0000000..401884f --- /dev/null +++ b/collection/roles/delete/409.json @@ -0,0 +1,3 @@ +{ + "message": "این نقش به یک یا چند کاربر اختصاص داده شده است." +} diff --git a/collection/roles/delete/index.bru b/collection/roles/delete/index.bru new file mode 100644 index 0000000..fac2b96 --- /dev/null +++ b/collection/roles/delete/index.bru @@ -0,0 +1,43 @@ +meta { + name: Delete Role + type: http + seq: 5 +} + +delete { + url: {{base_url}}/roles/{{role_id}} + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +docs { + ## Purpose + + Permanently deletes a role by UUID. + + ## Authentication + + Requires a valid access token with permission to delete roles. + + ## Behavior + + - A successful request returns no response body. + - The role can be deleted only when it has no user-role assignments. + - Move or remove all role assignments before deleting the role. + + ## Response + + - `200 OK` — Role deleted successfully with no response body. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot delete roles. + - `404 Not Found` — The role does not exist. + - `409 Conflict` — The role is assigned to one or more users. +} diff --git a/collection/roles/details/200.json b/collection/roles/details/200.json new file mode 100644 index 0000000..cf950df --- /dev/null +++ b/collection/roles/details/200.json @@ -0,0 +1,5 @@ +{ + "id": "3ecb1f52-6d6e-43b9-a8fb-4e0772c9f863", + "name": "کاربر", + "permissions": ["users.read"] +} diff --git a/collection/roles/details/401.json b/collection/roles/details/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/roles/details/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/roles/details/403.json b/collection/roles/details/403.json new file mode 100644 index 0000000..cec4788 --- /dev/null +++ b/collection/roles/details/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه مشاهده این نقش را ندارید." +} diff --git a/collection/roles/details/404.json b/collection/roles/details/404.json new file mode 100644 index 0000000..e6dccb8 --- /dev/null +++ b/collection/roles/details/404.json @@ -0,0 +1,3 @@ +{ + "message": "نقش موردنظر یافت نشد." +} diff --git a/collection/roles/details/index.bru b/collection/roles/details/index.bru new file mode 100644 index 0000000..ae0656c --- /dev/null +++ b/collection/roles/details/index.bru @@ -0,0 +1,40 @@ +meta { + name: Get Role + type: http + seq: 3 +} + +get { + url: {{base_url}}/roles/{{role_id}} + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +docs { + ## Purpose + + Returns a role by UUID. + + ## Authentication + + Requires a valid access token with permission to read roles. + + ## Behavior + + - The permissions field contains an array of permission name values. + + ## Response + + - `200 OK` — Role returned successfully. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot read roles. + - `404 Not Found` — The role does not exist. +} diff --git a/collection/roles/folder.bru b/collection/roles/folder.bru new file mode 100644 index 0000000..9ded2b5 --- /dev/null +++ b/collection/roles/folder.bru @@ -0,0 +1,4 @@ +meta { + name: Roles + seq: 16 +} diff --git a/collection/roles/list/200.json b/collection/roles/list/200.json new file mode 100644 index 0000000..de47fa1 --- /dev/null +++ b/collection/roles/list/200.json @@ -0,0 +1,7 @@ +[ + { + "id": "3ecb1f52-6d6e-43b9-a8fb-4e0772c9f863", + "name": "کاربر", + "permissions": ["users.read"] + } +] diff --git a/collection/roles/list/401.json b/collection/roles/list/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/roles/list/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/roles/list/403.json b/collection/roles/list/403.json new file mode 100644 index 0000000..bad8b88 --- /dev/null +++ b/collection/roles/list/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه مشاهده نقش‌ها را ندارید." +} diff --git a/collection/roles/list/index.bru b/collection/roles/list/index.bru new file mode 100644 index 0000000..7cad6db --- /dev/null +++ b/collection/roles/list/index.bru @@ -0,0 +1,40 @@ +meta { + name: List Roles + type: http + seq: 1 +} + +get { + url: {{base_url}}/roles + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +docs { + ## Purpose + + Returns all roles. + + ## Authentication + + Requires a valid access token with permission to read roles. + + ## Behavior + + - Each role includes its UUID, name, and an array of permission name values. + - Roles are access-control configuration. + + ## Response + + - `200 OK` — Roles returned successfully. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot read roles. +} diff --git a/collection/roles/update/200.json b/collection/roles/update/200.json new file mode 100644 index 0000000..cf950df --- /dev/null +++ b/collection/roles/update/200.json @@ -0,0 +1,5 @@ +{ + "id": "3ecb1f52-6d6e-43b9-a8fb-4e0772c9f863", + "name": "کاربر", + "permissions": ["users.read"] +} diff --git a/collection/roles/update/400.json b/collection/roles/update/400.json new file mode 100644 index 0000000..e233ae6 --- /dev/null +++ b/collection/roles/update/400.json @@ -0,0 +1,7 @@ +{ + "cause": { + "name": "نام نقش الزامی است یا قبلاً استفاده شده است.", + "permissions": "فهرست دسترسی‌ها معتبر نیست." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/roles/update/401.json b/collection/roles/update/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/roles/update/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/roles/update/403.json b/collection/roles/update/403.json new file mode 100644 index 0000000..eaee587 --- /dev/null +++ b/collection/roles/update/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه ویرایش این نقش را ندارید." +} diff --git a/collection/roles/update/404.json b/collection/roles/update/404.json new file mode 100644 index 0000000..e6dccb8 --- /dev/null +++ b/collection/roles/update/404.json @@ -0,0 +1,3 @@ +{ + "message": "نقش موردنظر یافت نشد." +} diff --git a/collection/roles/update/index.bru b/collection/roles/update/index.bru new file mode 100644 index 0000000..52dff65 --- /dev/null +++ b/collection/roles/update/index.bru @@ -0,0 +1,50 @@ +meta { + name: Update Role + type: http + seq: 4 +} + +patch { + url: {{base_url}}/roles/{{role_id}} + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json + Content-Type: application/json +} + +body:json { + { + "name": "کاربر", + "permissions": [] + } +} + +docs { + ## Purpose + + Updates a role by UUID. + + ## Authentication + + Requires a valid access token with permission to update roles. + + ## Behavior + + - The role name and permissions are replaced with the submitted values. + - permissions is an array of names returned by GET /permissions and may be empty. + + ## Response + + - `200 OK` — Role updated successfully. + - `400 Bad Request` — The role name or permissions are invalid. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot update roles. + - `404 Not Found` — The role does not exist. +} diff --git a/collection/users/create/201.json b/collection/users/create/201.json new file mode 100644 index 0000000..af4d8f8 --- /dev/null +++ b/collection/users/create/201.json @@ -0,0 +1,11 @@ +{ + "email": "user@example.com", + "first_name": "علی", + "id": "2dc56d6f-6b5a-4e0d-9b0b-9a1c9d7f8d41", + "is_system_admin": true, + "last_name": "رضایی", + "mobile": "09123456789", + "personnel_code": "EMP-1001", + "role_ids": ["3ecb1f52-6d6e-43b9-a8fb-4e0772c9f863"], + "username": "user" +} diff --git a/collection/users/create/400.json b/collection/users/create/400.json new file mode 100644 index 0000000..be1faf0 --- /dev/null +++ b/collection/users/create/400.json @@ -0,0 +1,10 @@ +{ + "cause": { + "first_name": "نام الزامی است.", + "last_name": "نام خانوادگی الزامی است.", + "mobile": "شماره موبایل معتبر نیست یا قبلاً ثبت شده است.", + "password": "رمز عبور الزامی است و باید شرایط لازم را داشته باشد.", + "personnel_code": "کد پرسنلی الزامی است یا قبلاً ثبت شده است." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/users/create/401.json b/collection/users/create/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/create/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/create/403.json b/collection/users/create/403.json new file mode 100644 index 0000000..e46c69b --- /dev/null +++ b/collection/users/create/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه ایجاد کاربر را ندارید." +} diff --git a/collection/users/create/index.bru b/collection/users/create/index.bru new file mode 100644 index 0000000..c1067c3 --- /dev/null +++ b/collection/users/create/index.bru @@ -0,0 +1,61 @@ +meta { + name: Create User + type: http + seq: 2 +} + +post { + url: {{base_url}}/users + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json + Content-Type: application/json +} + +body:json { + { + "first_name": "علی", + "last_name": "رضایی", + "mobile": "09123456789", + "password": "password123", + "personnel_code": "EMP-1001" + } +} + +docs { + ## Purpose + + Creates a user through the administrative user-management flow. + + ## Authentication + + Requires a valid access token with permission to create users. + + ## Behavior + + - Mobile numbers must be unique. + - Password is required and must satisfy the configured password policy. + - `personnel_code` is required and must be unique. + - The user is created with `status="active"` by default. + - `is_system_admin` cannot be supplied during creation. + - The dedicated system-administrator endpoint is the only way to change `is_system_admin`. + - No language preference is stored for the user. + - Email cannot be assigned through this endpoint and requires the OTP verification flow. + - Username is configured later by the user through the account endpoint. + - Roles are assigned later through the dedicated role endpoint. + - Activation may be changed later only through the dedicated status endpoint. + + ## Response + + - `201 Created` — User created successfully. + - `400 Bad Request` — Request validation failed or a unique field is already in use. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot create users. +} diff --git a/collection/users/delete/401.json b/collection/users/delete/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/delete/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/delete/403.json b/collection/users/delete/403.json new file mode 100644 index 0000000..d5af0de --- /dev/null +++ b/collection/users/delete/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه حذف این کاربر را ندارید." +} diff --git a/collection/users/delete/404.json b/collection/users/delete/404.json new file mode 100644 index 0000000..f649c1b --- /dev/null +++ b/collection/users/delete/404.json @@ -0,0 +1,3 @@ +{ + "message": "کاربر موردنظر پیدا نشد." +} diff --git a/collection/users/delete/409.json b/collection/users/delete/409.json new file mode 100644 index 0000000..ae0b41e --- /dev/null +++ b/collection/users/delete/409.json @@ -0,0 +1,3 @@ +{ + "message": "این کاربر به‌دلیل محدودیت‌های سیستم قابل حذف نیست." +} diff --git a/collection/users/delete/index.bru b/collection/users/delete/index.bru new file mode 100644 index 0000000..83b9b0b --- /dev/null +++ b/collection/users/delete/index.bru @@ -0,0 +1,42 @@ +meta { + name: Delete User + type: http + seq: 5 +} + +delete { + url: {{base_url}}/users/{{user_id}} + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +docs { + ## Purpose + + Deletes a user by UUID. + + ## Authentication + + Requires a valid access token with permission to delete users. + + ## Behavior + + - A successful request returns no response body. + - Deletion fails when it violates a protected system constraint. + + ## Response + + - `200 OK` — User deleted successfully with no response body. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot delete users. + - `404 Not Found` — The user does not exist. + - `409 Conflict` — The user cannot be deleted because of a system constraint. +} diff --git a/collection/users/details/200.json b/collection/users/details/200.json new file mode 100644 index 0000000..af4d8f8 --- /dev/null +++ b/collection/users/details/200.json @@ -0,0 +1,11 @@ +{ + "email": "user@example.com", + "first_name": "علی", + "id": "2dc56d6f-6b5a-4e0d-9b0b-9a1c9d7f8d41", + "is_system_admin": true, + "last_name": "رضایی", + "mobile": "09123456789", + "personnel_code": "EMP-1001", + "role_ids": ["3ecb1f52-6d6e-43b9-a8fb-4e0772c9f863"], + "username": "user" +} diff --git a/collection/users/details/401.json b/collection/users/details/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/details/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/details/403.json b/collection/users/details/403.json new file mode 100644 index 0000000..6c6009a --- /dev/null +++ b/collection/users/details/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه مشاهده این کاربر را ندارید." +} diff --git a/collection/users/details/404.json b/collection/users/details/404.json new file mode 100644 index 0000000..f649c1b --- /dev/null +++ b/collection/users/details/404.json @@ -0,0 +1,3 @@ +{ + "message": "کاربر موردنظر پیدا نشد." +} diff --git a/collection/users/details/index.bru b/collection/users/details/index.bru new file mode 100644 index 0000000..0ad02bd --- /dev/null +++ b/collection/users/details/index.bru @@ -0,0 +1,41 @@ +meta { + name: Get User + type: http + seq: 3 +} + +get { + url: {{base_url}}/users/{{user_id}} + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +docs { + ## Purpose + + Returns a user by UUID. + + ## Authentication + + Requires a valid access token with permission to read users. + + ## Behavior + + - Returns profile data, personnel code, `is_system_admin`, and assigned role IDs for the requested user. + - `is_system_admin` represents the global `SYSTEM_ADMIN` capability and is separate from role permissions. + + ## Response + + - `200 OK` — User returned successfully. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot read users. + - `404 Not Found` — The user does not exist. +} diff --git a/collection/users/folder.bru b/collection/users/folder.bru new file mode 100644 index 0000000..a2714cd --- /dev/null +++ b/collection/users/folder.bru @@ -0,0 +1,4 @@ +meta { + name: Users + seq: 21 +} diff --git a/collection/users/list/200.json b/collection/users/list/200.json new file mode 100644 index 0000000..8f287fc --- /dev/null +++ b/collection/users/list/200.json @@ -0,0 +1,27 @@ +{ + "items": [ + { + "email": "john.doe@example.com", + "first_name": "علی", + "id": "2dc56d6f-6b5a-4e0d-9b0b-9a1c9d7f8d41", + "is_system_admin": true, + "last_name": "رضایی", + "mobile": "09123456789", + "personnel_code": "EMP-1001", + "status": "active", + "username": "john.doe" + }, + { + "email": null, + "first_name": "سارا", + "id": "89a975c0-f6a4-4cb8-97c6-c558381b68d2", + "is_system_admin": false, + "last_name": "رضایی", + "mobile": "09120000000", + "personnel_code": "EMP-1002", + "status": "inactive", + "username": "jane.doe" + } + ], + "total": 2 +} diff --git a/collection/users/list/401.json b/collection/users/list/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/list/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/list/403.json b/collection/users/list/403.json new file mode 100644 index 0000000..1b5154e --- /dev/null +++ b/collection/users/list/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه مشاهده کاربران را ندارید." +} diff --git a/collection/users/list/index.bru b/collection/users/list/index.bru new file mode 100644 index 0000000..919586c --- /dev/null +++ b/collection/users/list/index.bru @@ -0,0 +1,59 @@ +meta { + name: List Users + type: http + seq: 1 +} + +get { + url: {{base_url}}/users + body: none + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json +} + +params:query { + email: + mobile: + name: + offset: 0 + size: 12 + status: + username: +} + +docs { + ## Purpose + + Returns a paginated list of users. + + ## Authentication + + Requires a valid access token with permission to read users. + + ## Behavior + + - `offset` specifies the number of users to skip and defaults to `0`. + - `size` specifies the maximum number of users to return and defaults to `12`. + - `email`, `mobile`, and `username` are optional exact-match filters. + - `status` is an optional activation-state filter accepting `active` or `inactive`. + - `name` performs a case-insensitive partial match against `first_name` and `last_name`. + - Provided filters are combined with AND logic. + - Invalid pagination values are ignored and replaced with their defaults. + - Invalid, empty, or unsupported query parameters are ignored. + - Each item includes summary profile data, personnel code, activation state, and `is_system_admin`. + - `is_system_admin` represents the global `SYSTEM_ADMIN` capability and is separate from role permissions. + - Roles are returned only when requesting a specific user. + + ## Response + + - `200 OK` — Users returned successfully. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot read users. +} diff --git a/collection/users/update-password/400.json b/collection/users/update-password/400.json new file mode 100644 index 0000000..cd14108 --- /dev/null +++ b/collection/users/update-password/400.json @@ -0,0 +1,6 @@ +{ + "cause": { + "password": "رمز عبور با سیاست امنیتی سامانه مطابقت ندارد." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/users/update-password/401.json b/collection/users/update-password/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/update-password/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/update-password/403.json b/collection/users/update-password/403.json new file mode 100644 index 0000000..65179c9 --- /dev/null +++ b/collection/users/update-password/403.json @@ -0,0 +1,3 @@ +{ + "message": "فقط مدیر سیستم اجازه تغییر رمز عبور کاربران را دارد." +} diff --git a/collection/users/update-password/404.json b/collection/users/update-password/404.json new file mode 100644 index 0000000..f649c1b --- /dev/null +++ b/collection/users/update-password/404.json @@ -0,0 +1,3 @@ +{ + "message": "کاربر موردنظر پیدا نشد." +} diff --git a/collection/users/update-password/index.bru b/collection/users/update-password/index.bru new file mode 100644 index 0000000..0144f37 --- /dev/null +++ b/collection/users/update-password/index.bru @@ -0,0 +1,53 @@ +meta { + name: Update User Password + type: http + seq: 10 +} + +put { + url: {{base_url}}/users/{{user_id}}/password + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json + Content-Type: application/json +} + +body:json { + { + "password": "NewPassword123!" + } +} + +docs { + ## Purpose + + Replaces a user's password. + + ## Authentication + + Requires a valid access token belonging to an existing `SYSTEM_ADMIN`. + + ## Behavior + + - `password` is required and must satisfy the configured password policy. + - The frontend sends the new password directly; no confirmation field is part of the API contract. + - The password must be stored only as a secure password hash. + - Password values and password hashes must never be returned. + - The operation is idempotent. + - A successful request returns no response body. + + ## Response + + - `200 OK` — Password updated successfully with no response body. + - `400 Bad Request` — The password is missing or does not satisfy the password policy. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user is not a `SYSTEM_ADMIN`. + - `404 Not Found` — The target user does not exist. +} diff --git a/collection/users/update-roles/400.json b/collection/users/update-roles/400.json new file mode 100644 index 0000000..103d759 --- /dev/null +++ b/collection/users/update-roles/400.json @@ -0,0 +1,6 @@ +{ + "cause": { + "role_ids": "شناسه‌های نقش انتخاب‌شده معتبر نیستند." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/users/update-roles/401.json b/collection/users/update-roles/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/update-roles/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/update-roles/403.json b/collection/users/update-roles/403.json new file mode 100644 index 0000000..6494ed1 --- /dev/null +++ b/collection/users/update-roles/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه تغییر نقش‌های این کاربر را ندارید." +} diff --git a/collection/users/update-roles/404.json b/collection/users/update-roles/404.json new file mode 100644 index 0000000..f649c1b --- /dev/null +++ b/collection/users/update-roles/404.json @@ -0,0 +1,3 @@ +{ + "message": "کاربر موردنظر پیدا نشد." +} diff --git a/collection/users/update-roles/409.json b/collection/users/update-roles/409.json new file mode 100644 index 0000000..566a957 --- /dev/null +++ b/collection/users/update-roles/409.json @@ -0,0 +1,3 @@ +{ + "message": "تغییر نقش‌های این کاربر با محدودیت‌های سیستم مغایرت دارد." +} diff --git a/collection/users/update-roles/index.bru b/collection/users/update-roles/index.bru new file mode 100644 index 0000000..6fda60d --- /dev/null +++ b/collection/users/update-roles/index.bru @@ -0,0 +1,54 @@ +meta { + name: Update User Roles + type: http + seq: 7 +} + +put { + url: {{base_url}}/users/{{user_id}}/roles + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json + Content-Type: application/json +} + +body:json { + { + "role_ids": [ + "{{role_id}}" + ] + } +} + +docs { + ## Purpose + + Replaces the complete role assignment of a user. + + ## Authentication + + Requires a valid access token with permission to update user roles. + + ## Behavior + + - Every role ID must reference an existing role. + - Roles omitted from `role_ids` are removed from the user. + - The operation is idempotent. + - A successful request returns no response body. + + ## Response + + - `200 OK` — User roles updated successfully with no response body. + - `400 Bad Request` — The role-ID list is missing or invalid. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot update user roles. + - `404 Not Found` — The user does not exist. + - `409 Conflict` — The role change violates a system constraint. +} diff --git a/collection/users/update-status/400.json b/collection/users/update-status/400.json new file mode 100644 index 0000000..509908e --- /dev/null +++ b/collection/users/update-status/400.json @@ -0,0 +1,6 @@ +{ + "cause": { + "status": "وضعیت کاربر باید active یا inactive باشد." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/users/update-status/401.json b/collection/users/update-status/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/update-status/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/update-status/403.json b/collection/users/update-status/403.json new file mode 100644 index 0000000..93d6f04 --- /dev/null +++ b/collection/users/update-status/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه تغییر وضعیت این کاربر را ندارید." +} diff --git a/collection/users/update-status/404.json b/collection/users/update-status/404.json new file mode 100644 index 0000000..f649c1b --- /dev/null +++ b/collection/users/update-status/404.json @@ -0,0 +1,3 @@ +{ + "message": "کاربر موردنظر پیدا نشد." +} diff --git a/collection/users/update-status/409.json b/collection/users/update-status/409.json new file mode 100644 index 0000000..78b3e1d --- /dev/null +++ b/collection/users/update-status/409.json @@ -0,0 +1,3 @@ +{ + "message": "تغییر وضعیت این کاربر با محدودیت‌های سیستم مغایرت دارد." +} diff --git a/collection/users/update-status/index.bru b/collection/users/update-status/index.bru new file mode 100644 index 0000000..090efc8 --- /dev/null +++ b/collection/users/update-status/index.bru @@ -0,0 +1,51 @@ +meta { + name: Update User Status + type: http + seq: 6 +} + +patch { + url: {{base_url}}/users/{{user_id}}/status + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json + Content-Type: application/json +} + +body:json { + { + "status": "active" + } +} + +docs { + ## Purpose + + Updates the activation state of a user. + + ## Authentication + + Requires a valid access token with permission to update user activation. + + ## Behavior + + - `status` must be either `"active"` or `"inactive"`. + - The operation is idempotent. + - A successful request returns no response body. + + ## Response + + - `200 OK` — User activation updated successfully with no response body. + - `400 Bad Request` — `status` is missing or invalid. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot update user activation. + - `404 Not Found` — The user does not exist. + - `409 Conflict` — The activation change violates a system constraint. +} diff --git a/collection/users/update-system-admin/400.json b/collection/users/update-system-admin/400.json new file mode 100644 index 0000000..989dd15 --- /dev/null +++ b/collection/users/update-system-admin/400.json @@ -0,0 +1,6 @@ +{ + "cause": { + "is_system_admin": "مقدار مدیر سیستم باید true یا false باشد." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/users/update-system-admin/401.json b/collection/users/update-system-admin/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/update-system-admin/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/update-system-admin/403.json b/collection/users/update-system-admin/403.json new file mode 100644 index 0000000..3213a48 --- /dev/null +++ b/collection/users/update-system-admin/403.json @@ -0,0 +1,3 @@ +{ + "message": "فقط مدیر سیستم می‌تواند دسترسی مدیر سیستم را تغییر دهد." +} diff --git a/collection/users/update-system-admin/404.json b/collection/users/update-system-admin/404.json new file mode 100644 index 0000000..7e7ecd7 --- /dev/null +++ b/collection/users/update-system-admin/404.json @@ -0,0 +1,3 @@ +{ + "message": "کاربر موردنظر یافت نشد." +} diff --git a/collection/users/update-system-admin/index.bru b/collection/users/update-system-admin/index.bru new file mode 100644 index 0000000..376eb94 --- /dev/null +++ b/collection/users/update-system-admin/index.bru @@ -0,0 +1,54 @@ +meta { + name: Update User System Administrator + type: http + seq: 9 +} + +patch { + url: {{base_url}}/users/{{user_id}}/system-admin + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json + Content-Type: application/json +} + +body:json { + { + "is_system_admin": true + } +} + +docs { + ## Purpose + + Grants or revokes the global `SYSTEM_ADMIN` capability for a user. + + ## Authentication + + Requires a valid access token belonging to an existing `SYSTEM_ADMIN`. + + ## Behavior + + - `is_system_admin` is required and must be a boolean. + - Setting `is_system_admin` to `true` grants the global `SYSTEM_ADMIN` capability. + - Setting `is_system_admin` to `false` revokes the global `SYSTEM_ADMIN` capability. + - `SYSTEM_ADMIN` is separate from roles and assignable permissions. + - This is the only endpoint allowed to change `is_system_admin`. + - The operation is idempotent. + - A successful request returns no response body. + + ## Response + + - `200 OK` — System-administrator capability updated successfully with no response body. + - `400 Bad Request` — `is_system_admin` is missing or invalid. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user is not a `SYSTEM_ADMIN`. + - `404 Not Found` — The target user does not exist. +} diff --git a/collection/users/update/200.json b/collection/users/update/200.json new file mode 100644 index 0000000..af4d8f8 --- /dev/null +++ b/collection/users/update/200.json @@ -0,0 +1,11 @@ +{ + "email": "user@example.com", + "first_name": "علی", + "id": "2dc56d6f-6b5a-4e0d-9b0b-9a1c9d7f8d41", + "is_system_admin": true, + "last_name": "رضایی", + "mobile": "09123456789", + "personnel_code": "EMP-1001", + "role_ids": ["3ecb1f52-6d6e-43b9-a8fb-4e0772c9f863"], + "username": "user" +} diff --git a/collection/users/update/400.json b/collection/users/update/400.json new file mode 100644 index 0000000..b13e7e0 --- /dev/null +++ b/collection/users/update/400.json @@ -0,0 +1,9 @@ +{ + "cause": { + "first_name": "نام معتبر نیست.", + "last_name": "نام خانوادگی معتبر نیست.", + "mobile": "شماره موبایل معتبر نیست یا قبلاً ثبت شده است.", + "personnel_code": "کد پرسنلی معتبر نیست یا قبلاً ثبت شده است." + }, + "message": "اطلاعات ارسال‌شده معتبر نیست." +} diff --git a/collection/users/update/401.json b/collection/users/update/401.json new file mode 100644 index 0000000..6ddf298 --- /dev/null +++ b/collection/users/update/401.json @@ -0,0 +1,3 @@ +{ + "message": "احراز هویت شما معتبر نیست یا منقضی شده است." +} diff --git a/collection/users/update/403.json b/collection/users/update/403.json new file mode 100644 index 0000000..2748160 --- /dev/null +++ b/collection/users/update/403.json @@ -0,0 +1,3 @@ +{ + "message": "اجازه ویرایش این کاربر را ندارید." +} diff --git a/collection/users/update/404.json b/collection/users/update/404.json new file mode 100644 index 0000000..f649c1b --- /dev/null +++ b/collection/users/update/404.json @@ -0,0 +1,3 @@ +{ + "message": "کاربر موردنظر پیدا نشد." +} diff --git a/collection/users/update/index.bru b/collection/users/update/index.bru new file mode 100644 index 0000000..b2dd9b5 --- /dev/null +++ b/collection/users/update/index.bru @@ -0,0 +1,59 @@ +meta { + name: Update User + type: http + seq: 4 +} + +patch { + url: {{base_url}}/users/{{user_id}} + body: json + auth: bearer +} + +auth:bearer { + token: {{access_token}} +} + +headers { + Accept: application/json + Content-Type: application/json +} + +body:json { + { + "first_name": "علی", + "last_name": "رضایی", + "mobile": "09123456789", + "personnel_code": "EMP-1001" + } +} + +docs { + ## Purpose + + Partially updates a user by UUID. + + ## Authentication + + Requires a valid access token with permission to update users. + + ## Behavior + + - Only fields included in the request body are updated. + - Omitted fields remain unchanged. + - At least one supported field must be provided. + - `personnel_code` must remain unique when changed. + - Email cannot be changed through this endpoint and requires the OTP verification flow. + - Language cannot be changed per user; the administrator-selected system language applies to everyone. + - Username is managed by the user through the account username endpoint. + - Activation, roles, and `is_system_admin` cannot be changed through this endpoint. + - `is_system_admin` is changed only through the dedicated system-administrator endpoint. + + ## Response + + - `200 OK` — User updated successfully. + - `400 Bad Request` — Request validation failed or a unique field is already in use. + - `401 Unauthorized` — Authentication is missing or invalid. + - `403 Forbidden` — The authenticated user cannot update users. + - `404 Not Found` — The user does not exist. +} diff --git a/docs/api-client.md b/docs/api-client.md index b4f068b..c0e02d9 100644 --- a/docs/api-client.md +++ b/docs/api-client.md @@ -38,29 +38,27 @@ apiClient.blob(url, config); ## Endpoint Examples -| Helper | Method | Endpoint | -| --------------------------- | -------- | ------------------------- | -| `login` | `POST` | `/auth/login` | -| `getAccount` | `GET` | `/account/me` | -| `changePassword` | `POST` | `/auth/change-password` | -| `fetchUsers` | `GET` | `/users` | -| `fetchUser` | `GET` | `/users/:id` | -| `createUser` | `POST` | `/users` | -| `updateUser` | `PATCH` | `/users/:id` | -| `deleteUser` | `DELETE` | `/users/:id` | -| `updateUserRoles` | `PUT` | `/users/:id/roles` | -| `updateUserWorkspaces` | `PUT` | `/users/:id/workspaces` | -| `updateUserPassword` | `PUT` | `/users/:id/password` | -| `updateUserStatus` | `PATCH` | `/users/:id/status` | -| `updateUserSystemAdmin` | `PATCH` | `/users/:id/system-admin` | -| `fetchUserRoleOptions` | `GET` | `/roles` | -| `fetchUserWorkspaceOptions` | `GET` | `/workspaces` | -| `fetchRoles` | `GET` | `/roles` | -| `fetchRole` | `GET` | `/roles/:id` | -| `createRole` | `POST` | `/roles` | -| `updateRole` | `PATCH` | `/roles/:id` | -| `deleteRole` | `DELETE` | `/roles/:id` | -| `fetchPermissions` | `GET` | `/permissions` | +| Helper | Method | Endpoint | +| ----------------------- | -------- | ------------------------- | +| `login` | `POST` | `/auth/login` | +| `getAccount` | `GET` | `/account/me` | +| `changePassword` | `POST` | `/auth/change-password` | +| `fetchUsers` | `GET` | `/users` | +| `fetchUser` | `GET` | `/users/:id` | +| `createUser` | `POST` | `/users` | +| `updateUser` | `PATCH` | `/users/:id` | +| `deleteUser` | `DELETE` | `/users/:id` | +| `updateUserRoles` | `PUT` | `/users/:id/roles` | +| `updateUserPassword` | `PUT` | `/users/:id/password` | +| `updateUserStatus` | `PATCH` | `/users/:id/status` | +| `updateUserSystemAdmin` | `PATCH` | `/users/:id/system-admin` | +| `fetchUserRoleOptions` | `GET` | `/roles` | +| `fetchRoles` | `GET` | `/roles` | +| `fetchRole` | `GET` | `/roles/:id` | +| `createRole` | `POST` | `/roles` | +| `updateRole` | `PATCH` | `/roles/:id` | +| `deleteRole` | `DELETE` | `/roles/:id` | +| `fetchPermissions` | `GET` | `/permissions` | ## Data Shape Notes diff --git a/docs/security/authentication-review.md b/docs/security/authentication-review.md index f46c4be..efaef18 100644 --- a/docs/security/authentication-review.md +++ b/docs/security/authentication-review.md @@ -30,7 +30,7 @@ cookie, token, account-recovery, or identity-provider change. - [ ] Password and OTP reset artifacts are single-use, short-lived, and never exposed in logs or URLs beyond the minimum required flow. - [ ] Authorization is enforced server-side for every object and operation, - including object ownership and tenant/workspace boundaries. + including object ownership and tenant boundaries. - [ ] Authentication responses and errors do not enable account enumeration. - [ ] Session creation, refresh, revocation, privilege changes, and suspicious failures create protected audit events. diff --git a/e2e/management.spec.ts b/e2e/management.spec.ts index b991ca7..3432b79 100644 --- a/e2e/management.spec.ts +++ b/e2e/management.spec.ts @@ -191,9 +191,6 @@ test.describe("role and user management", () => { await page.route("https://api.example.com/roles", async (route) => { await route.fulfill({ contentType: "application/json", json: [] }); }); - await page.route("https://api.example.com/workspaces", async (route) => { - await route.fulfill({ contentType: "application/json", json: [] }); - }); await page.route( /https:\/\/api\.example\.com\/users(?:\?.*)?$/, async (route) => { @@ -218,7 +215,7 @@ test.describe("role and user management", () => { }); await route.fulfill({ contentType: "application/json", - json: { ...users[0], role_ids: [], workspace_ids: [] }, + json: { ...users[0], role_ids: [] }, }); return; } @@ -240,7 +237,7 @@ test.describe("role and user management", () => { users[0] = { ...users[0], first_name: "Augusta" }; await route.fulfill({ contentType: "application/json", - json: { ...users[0], role_ids: [], workspace_ids: [] }, + json: { ...users[0], role_ids: [] }, }); return; } @@ -256,7 +253,7 @@ test.describe("role and user management", () => { await route.fulfill({ contentType: "application/json", - json: { ...users[0], role_ids: [], workspace_ids: [] }, + json: { ...users[0], role_ids: [] }, }); }); await page.goto("/users"); diff --git a/src/features/users/Users.stories.tsx b/src/features/users/Users.stories.tsx index ed52cbc..d93755d 100644 --- a/src/features/users/Users.stories.tsx +++ b/src/features/users/Users.stories.tsx @@ -9,12 +9,7 @@ import { } from "@/test/storybook/fixtures"; import { StoryShell } from "@/test/storybook/StoryShell"; -import { - fetchUser, - fetchUserRoleOptions, - fetchUsers, - fetchUserWorkspaceOptions, -} from "./api"; +import { fetchUser, fetchUserRoleOptions, fetchUsers } from "./api"; import { UsersPage } from "./Users"; const meta = { @@ -22,7 +17,6 @@ const meta = { mocked(fetchUser).mockResolvedValue(user); mocked(fetchUserRoleOptions).mockResolvedValue(userOptions); mocked(fetchUsers).mockResolvedValue({ items: users, total: users.length }); - mocked(fetchUserWorkspaceOptions).mockResolvedValue(userOptions); }, component: UsersPage, render: () => ( diff --git a/src/features/users/Users.tsx b/src/features/users/Users.tsx index 33433d0..05c661f 100644 --- a/src/features/users/Users.tsx +++ b/src/features/users/Users.tsx @@ -24,7 +24,6 @@ import { useActionPermissions, useAntd, useCore } from "@/app/hooks"; import { UserForm } from "@/features/users/forms/user/User"; import { UserPasswordForm } from "@/features/users/forms/user-password/UserPassword"; import { UserFormRole } from "@/features/users/forms/user-role/UserRole"; -import { UserWorkspaceForm } from "@/features/users/forms/user-workspace/UserWorkspace"; import { modalKeys } from "@/shared/config"; import { useFilterParams } from "@/shared/hooks"; import { getErrorMessage } from "@/shared/lib"; @@ -36,7 +35,6 @@ import { fetchUser, fetchUserRoleOptions, fetchUsers, - fetchUserWorkspaceOptions, updateUserStatus, updateUserSystemAdmin, } from "./api"; @@ -55,7 +53,6 @@ export const UsersPage = () => { const [roles, setRoles] = useState([]); const [selectedData, setSelectedData] = useState(); const [total, setTotal] = useState(0); - const [workspaces, setWorkspaces] = useState([]); const { messageAPI, modalAPI } = useAntd(); const { canCreateUsers, canDeleteUsers, canUpdateUsers } = useActionPermissions(); @@ -247,16 +244,6 @@ export const UsersPage = () => { type="text" /> - -