refactor(auth): domain-specific role names (consistent convention)

User feedback: универсальный 'ordinis:approver' отступал от convention
'ordinis:<domain>:<action>'. Меняю на domain-specific:

WorkflowRoles constants:
- APPROVER → SCHEMA_REVIEWER ('ordinis:schema:reviewer')
- PUBLISHER → SCHEMA_PUBLISHER ('ordinis:schema:publisher')
- Новая RECORD_REVIEWER ('ordinis:record:reviewer')

Applied:
- SchemaDraftService.decide() → require SCHEMA_REVIEWER
- SchemaDraftService.publish() → require SCHEMA_PUBLISHER
- DraftService.approve() / .reject() → require RECORD_REVIEWER

Frontend usePermissions.ts:
- ROLE_APPROVER + ROLE_PUBLISHER → ROLE_SCHEMA_REVIEWER +
  ROLE_SCHEMA_PUBLISHER + ROLE_RECORD_REVIEWER
- Новый hook useCanReviewRecord() для record draft actions

Keycloak setup (updated):
- ordinis:schema:reviewer
- ordinis:schema:publisher
- ordinis:record:reviewer
(Composite role 'ordinis:admin' = all три + scope roles — для удобства.)
This commit is contained in:
Zimin A.N.
2026-05-13 12:44:23 +03:00
parent 24aa94d130
commit c5b00ff623
6 changed files with 68 additions and 54 deletions
+22 -14
View File
@@ -4,10 +4,9 @@ import { useAuth } from 'react-oidc-context'
* Permission helpers для schema + record workflow. Phase 1d — **ACTIVE**:
* читает {@code realm_access.roles} из JWT и гейтит actions.
*
* <p>Naming convention: colon-separated namespace
* {@code ordinis:<domain>:<action>} consistent с scope ACL ролями
* ({@code ordinis:client:public}). Backend parser в
* {@code ScopeContext.java} / {@code WorkflowRoles.java}.
* <p>Naming convention: colon-separated {@code ordinis:<domain>:<action>}
* pattern (consistent с scope ACL {@code ordinis:client:public}). Backend
* parser в {@code WorkflowRoles.java}.
*
* <p>Backend enforcement (см. {@code WorkflowRoles}):
* <ul>
@@ -21,13 +20,19 @@ import { useAuth } from 'react-oidc-context'
* backend всё равно 403'нет на прямой API call.
*
* <p><b>Migration note (Phase 1d enforcement):</b> Keycloak realm `nstart`
* должен иметь созданные роли {@code ordinis:approver} и
* {@code ordinis:publisher}, и actual reviewer-users должны их получить.
* До этого все decide / publish операции вернут {@code 403 forbidden_role}.
* должен иметь созданные роли:
* <ul>
* <li>{@code ordinis:schema:reviewer} — schema decide</li>
* <li>{@code ordinis:schema:publisher} — schema publish</li>
* <li>{@code ordinis:record:reviewer} — record approve/reject</li>
* </ul>
* и actual reviewer-users должны их получить. До этого все decide /
* publish операции вернут {@code 403 forbidden_role}.
*/
export const ROLE_APPROVER = 'ordinis:approver'
export const ROLE_PUBLISHER = 'ordinis:publisher'
export const ROLE_SCHEMA_REVIEWER = 'ordinis:schema:reviewer'
export const ROLE_SCHEMA_PUBLISHER = 'ordinis:schema:publisher'
export const ROLE_RECORD_REVIEWER = 'ordinis:record:reviewer'
/** Достаёт realm roles из JWT (Keycloak claim path: realm_access.roles). */
function realmRoles(profile: unknown): string[] {
@@ -43,19 +48,22 @@ function hasRole(profile: unknown, role: string): boolean {
return realmRoles(profile).includes(role)
}
/** Может ли актор approve / request_changes / reject schema OR record draft. */
/** Может ли актор approve / request_changes / reject schema draft. */
export function useCanReviewSchema(): boolean {
const auth = useAuth()
return auth.isAuthenticated && hasRole(auth.user?.profile, ROLE_APPROVER)
return auth.isAuthenticated && hasRole(auth.user?.profile, ROLE_SCHEMA_REVIEWER)
}
/** Universal alias — то же что {@link useCanReviewSchema}. */
export const useCanReviewDraft = useCanReviewSchema
/** Может ли актор approve / reject record draft. */
export function useCanReviewRecord(): boolean {
const auth = useAuth()
return auth.isAuthenticated && hasRole(auth.user?.profile, ROLE_RECORD_REVIEWER)
}
/** Может ли актор publish approved schema draft (bump live version). */
export function useCanPublishSchema(): boolean {
const auth = useAuth()
return auth.isAuthenticated && hasRole(auth.user?.profile, ROLE_PUBLISHER)
return auth.isAuthenticated && hasRole(auth.user?.profile, ROLE_SCHEMA_PUBLISHER)
}
/**