@rashidee/co2 1.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. package/dist/index.js +25449 -0
  2. package/drizzle/0000_remarkable_charles_xavier.sql +210 -0
  3. package/drizzle/0001_wise_wolf_cub.sql +13 -0
  4. package/drizzle/0002_secret_spyke.sql +16 -0
  5. package/drizzle/0003_workspace_config_singleton.sql +16 -0
  6. package/drizzle/0004_terminal_claim_v120.sql +11 -0
  7. package/drizzle/0005_brainstorm_v124.sql +10 -0
  8. package/drizzle/0006_project_v140.sql +6 -0
  9. package/drizzle/0007_prd_wizard_v124.sql +14 -0
  10. package/drizzle/0008_workflow_artifact_v126.sql +24 -0
  11. package/drizzle/0009_project_import_v150.sql +7 -0
  12. package/drizzle/0010_quality_v151.sql +3 -0
  13. package/drizzle/meta/0000_snapshot.json +1335 -0
  14. package/drizzle/meta/0001_snapshot.json +1416 -0
  15. package/drizzle/meta/0002_snapshot.json +1510 -0
  16. package/drizzle/meta/_journal.json +83 -0
  17. package/package.json +41 -0
  18. package/plugin/.claude-plugin/marketplace.json +36 -0
  19. package/plugin/.claude-plugin/plugin.json +22 -0
  20. package/plugin/README.md +625 -0
  21. package/plugin/SKILLS.md +350 -0
  22. package/plugin/co2.svg +5 -0
  23. package/plugin/commands/brainstorm-loop.md +129 -0
  24. package/plugin/skills/conductor-defect/SKILL.md +898 -0
  25. package/plugin/skills/conductor-feature-develop/SKILL.md +1383 -0
  26. package/plugin/skills/conductor-feature-develop/references/implementation-domain-template.md +131 -0
  27. package/plugin/skills/conductor-feature-develop/references/implementation-master-template.md +89 -0
  28. package/plugin/skills/conductor-feature-develop/references/playwright-setup.md +224 -0
  29. package/plugin/skills/conductor-feature-prepare/SKILL.md +604 -0
  30. package/plugin/skills/depgen-k8s/SKILL.md +468 -0
  31. package/plugin/skills/depgen-k8s/references/dockerfile-laravel.md +197 -0
  32. package/plugin/skills/depgen-k8s/references/dockerfile-nodejs.md +163 -0
  33. package/plugin/skills/depgen-k8s/references/dockerfile-spring-boot.md +174 -0
  34. package/plugin/skills/depgen-k8s/references/k8s-patterns.md +869 -0
  35. package/plugin/skills/mockgen-shadcn/SKILL.md +1067 -0
  36. package/plugin/skills/mockgen-shadcn/references/admin-layout-template.md +1467 -0
  37. package/plugin/skills/mockgen-shadcn/references/mockup-hub-template.md +498 -0
  38. package/plugin/skills/mockgen-shadcn/references/mockup-index-template.md +216 -0
  39. package/plugin/skills/mockgen-tailwind/SKILL.md +904 -0
  40. package/plugin/skills/mockgen-tailwind/references/admin-layout-template.md +720 -0
  41. package/plugin/skills/mockgen-tailwind/references/mockup-hub-template.md +498 -0
  42. package/plugin/skills/mockgen-tailwind/references/mockup-index-template.md +190 -0
  43. package/plugin/skills/modelgen-nosql/SKILL.md +617 -0
  44. package/plugin/skills/modelgen-nosql/references/model-extraction-methodology.md +497 -0
  45. package/plugin/skills/modelgen-nosql/references/nosql-design-guide.md +258 -0
  46. package/plugin/skills/modelgen-relational/SKILL.md +707 -0
  47. package/plugin/skills/modelgen-relational/references/model-extraction-methodology.md +499 -0
  48. package/plugin/skills/specgen-flutter-riverpod/SKILL.md +898 -0
  49. package/plugin/skills/specgen-flutter-riverpod/references/component-patterns.md +971 -0
  50. package/plugin/skills/specgen-flutter-riverpod/references/notification-patterns.md +457 -0
  51. package/plugin/skills/specgen-flutter-riverpod/references/routing-patterns.md +362 -0
  52. package/plugin/skills/specgen-flutter-riverpod/references/security-patterns.md +709 -0
  53. package/plugin/skills/specgen-flutter-riverpod/references/spec-template.md +1566 -0
  54. package/plugin/skills/specgen-flutter-riverpod/references/state-patterns.md +518 -0
  55. package/plugin/skills/specgen-flutter-riverpod/references/storage-patterns.md +370 -0
  56. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/SKILL.md +984 -0
  57. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/batch-patterns.md +359 -0
  58. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/messaging-patterns.md +468 -0
  59. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/modulith-patterns.md +461 -0
  60. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/reporting-patterns.md +803 -0
  61. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/security-patterns.md +501 -0
  62. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/spec-template.md +1268 -0
  63. package/plugin/skills/specgen-laravel-eloquent-bladehtmx/references/web-patterns.md +301 -0
  64. package/plugin/skills/specgen-node-cli-web/SKILL.md +561 -0
  65. package/plugin/skills/specgen-node-cli-web/references/auth-patterns.md +293 -0
  66. package/plugin/skills/specgen-node-cli-web/references/cli-packaging-patterns.md +250 -0
  67. package/plugin/skills/specgen-node-cli-web/references/server-patterns.md +247 -0
  68. package/plugin/skills/specgen-node-cli-web/references/spec-template.md +401 -0
  69. package/plugin/skills/specgen-node-cli-web/references/testing-patterns.md +174 -0
  70. package/plugin/skills/specgen-react-mui/SKILL.md +789 -0
  71. package/plugin/skills/specgen-react-mui/references/component-patterns.md +1077 -0
  72. package/plugin/skills/specgen-react-mui/references/reporting-patterns.md +1998 -0
  73. package/plugin/skills/specgen-react-mui/references/routing-patterns.md +461 -0
  74. package/plugin/skills/specgen-react-mui/references/security-patterns.md +599 -0
  75. package/plugin/skills/specgen-react-mui/references/spec-template.md +1030 -0
  76. package/plugin/skills/specgen-react-mui/references/state-patterns.md +327 -0
  77. package/plugin/skills/specgen-react-tailwind/SKILL.md +859 -0
  78. package/plugin/skills/specgen-react-tailwind/references/component-patterns.md +1212 -0
  79. package/plugin/skills/specgen-react-tailwind/references/reporting-patterns.md +1529 -0
  80. package/plugin/skills/specgen-react-tailwind/references/routing-patterns.md +669 -0
  81. package/plugin/skills/specgen-react-tailwind/references/security-patterns.md +656 -0
  82. package/plugin/skills/specgen-react-tailwind/references/spec-template.md +1237 -0
  83. package/plugin/skills/specgen-react-tailwind/references/state-patterns.md +327 -0
  84. package/plugin/skills/specgen-sdk-java/SKILL.md +933 -0
  85. package/plugin/skills/specgen-sdk-java/references/api-extraction-patterns.md +247 -0
  86. package/plugin/skills/specgen-sdk-java/references/http-client-patterns.md +495 -0
  87. package/plugin/skills/specgen-sdk-java/references/packaging-patterns.md +366 -0
  88. package/plugin/skills/specgen-sdk-java/references/spec-template.md +956 -0
  89. package/plugin/skills/specgen-spring-jpa-jtehtmx/SKILL.md +1284 -0
  90. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/batch-patterns.md +1224 -0
  91. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/jasper-patterns.md +1638 -0
  92. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/messaging-patterns.md +512 -0
  93. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/modulith-patterns.md +354 -0
  94. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/security-patterns.md +551 -0
  95. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/spec-template.md +2502 -0
  96. package/plugin/skills/specgen-spring-jpa-jtehtmx/references/web-patterns.md +503 -0
  97. package/plugin/skills/specgen-spring-jpa-restapi/SKILL.md +990 -0
  98. package/plugin/skills/specgen-spring-jpa-restapi/references/batch-patterns.md +1224 -0
  99. package/plugin/skills/specgen-spring-jpa-restapi/references/messaging-patterns.md +512 -0
  100. package/plugin/skills/specgen-spring-jpa-restapi/references/modulith-patterns.md +279 -0
  101. package/plugin/skills/specgen-spring-jpa-restapi/references/reporting-patterns.md +1536 -0
  102. package/plugin/skills/specgen-spring-jpa-restapi/references/restapi-patterns.md +722 -0
  103. package/plugin/skills/specgen-spring-jpa-restapi/references/security-patterns.md +351 -0
  104. package/plugin/skills/specgen-spring-jpa-restapi/references/spec-template.md +553 -0
  105. package/plugin/skills/specgen-ts-cli/SKILL.md +861 -0
  106. package/plugin/skills/specgen-ts-cli/references/async-patterns.md +1101 -0
  107. package/plugin/skills/specgen-ts-cli/references/command-patterns.md +383 -0
  108. package/plugin/skills/specgen-ts-cli/references/config-patterns.md +430 -0
  109. package/plugin/skills/specgen-ts-cli/references/database-patterns.md +750 -0
  110. package/plugin/skills/specgen-ts-cli/references/packaging-patterns.md +426 -0
  111. package/plugin/skills/specgen-ts-cli/references/spec-template.md +1391 -0
  112. package/plugin/skills/specgen-ts-cli/references/testing-patterns.md +909 -0
  113. package/plugin/skills/testgen-functional/SKILL.md +1381 -0
  114. package/plugin/skills/tracegen-matrix/SKILL.md +243 -0
  115. package/plugin/skills/util-preparek8senv/SKILL.md +422 -0
  116. package/plugin/skills/util-projectinit/SKILL.md +307 -0
  117. package/plugin/skills/util-projectinit/reference/brainstorm-loop.md +129 -0
  118. package/plugin/skills/util-projectinit/reference/brainstorm-protocol.md +144 -0
  119. package/plugin/skills/util-projectinit/reference/brainstorm-setup-guide.md +153 -0
  120. package/plugin/skills/util-projectinit/reference/brainstorm-template-appdev.md +87 -0
  121. package/plugin/skills/util-projectinit/reference/co2-context-generation-guide.md +761 -0
  122. package/plugin/skills/util-projectsync/SKILL.md +501 -0
  123. package/plugin/skills/util-updprd/SKILL.md +324 -0
  124. package/plugin/skills/util-usanalyzer/SKILL.md +307 -0
  125. package/plugin/skills/util-ustagger/SKILL.md +154 -0
  126. package/static/assets/abnfDiagram-VRR7QNED-DvPuwyCV.js +1 -0
  127. package/static/assets/arc-DRKw7sjs.js +1 -0
  128. package/static/assets/architectureDiagram-ZJ3FMSHR-CaNcxUbC.js +36 -0
  129. package/static/assets/blockDiagram-677ZJIJ3-Demd4AOK.js +132 -0
  130. package/static/assets/c4Diagram-LMCZKHZV-PVKUfc2x.js +10 -0
  131. package/static/assets/channel-C2nVbuwv.js +1 -0
  132. package/static/assets/chunk-2Q5K7J3B-CExf8ww_.js +1 -0
  133. package/static/assets/chunk-32BRIVSS-CFPaoONF.js +1 -0
  134. package/static/assets/chunk-5VM5RSS4-DE2esM67.js +15 -0
  135. package/static/assets/chunk-EX3LRPZG-SON_YdDG.js +231 -0
  136. package/static/assets/chunk-JWPE2WC7-Qxah8LNr.js +1 -0
  137. package/static/assets/chunk-MOJQB5TN-BcWGQV3j.js +88 -0
  138. package/static/assets/chunk-RYQCIY6F-DkDj-OR5.js +1 -0
  139. package/static/assets/chunk-V7JOEXUC-BlI1Mrah.js +206 -0
  140. package/static/assets/chunk-VR4S4FIN-Agf__duj.js +1 -0
  141. package/static/assets/chunk-XXDRQBXY-B5CfAh85.js +1 -0
  142. package/static/assets/classDiagram-OUVF2IWQ-DXJIljsR.js +1 -0
  143. package/static/assets/classDiagram-v2-EOCWNBFH-DXJIljsR.js +1 -0
  144. package/static/assets/cose-bilkent-JH36ORCC-DO0cQkcy.js +1 -0
  145. package/static/assets/cynefin-VYW2F7L2-CfOnsnXc.js +178 -0
  146. package/static/assets/cynefinDiagram-TSTJHNR4-BJ8tULAv.js +62 -0
  147. package/static/assets/cytoscape.esm-DTSO7Bv0.js +331 -0
  148. package/static/assets/dagre-VKFMJZFB-DfjwzHqP.js +4 -0
  149. package/static/assets/defaultLocale-DX6XiGOO.js +1 -0
  150. package/static/assets/diagram-FQU43EPY-CndSOIIM.js +3 -0
  151. package/static/assets/diagram-G47NLZAW-Czf5hN0Z.js +24 -0
  152. package/static/assets/diagram-NH7WQ7WH-COAyojGh.js +24 -0
  153. package/static/assets/diagram-OA4YK3LP-H8PoiN2m.js +30 -0
  154. package/static/assets/diagram-WEI45ONY-DzcaVzjp.js +41 -0
  155. package/static/assets/ebnfDiagram-CCIWWBDH-DPazGYLJ.js +1 -0
  156. package/static/assets/erDiagram-Q63AITRT-Bo-fubgF.js +85 -0
  157. package/static/assets/flowDiagram-23GEKE2U-CSvxUDwh.js +156 -0
  158. package/static/assets/ganttDiagram-NO4QXBWP-DnBveWX6.js +292 -0
  159. package/static/assets/gitGraphDiagram-IHSO6WYX-B3rVL8c-.js +106 -0
  160. package/static/assets/graph-C9eacEi8.js +1 -0
  161. package/static/assets/index-LZSQkLE2.css +41 -0
  162. package/static/assets/index-y7XDSgMG.js +495 -0
  163. package/static/assets/infoDiagram-FWYZ7A6U-CvWV9yy2.js +2 -0
  164. package/static/assets/init-Gi6I4Gst.js +1 -0
  165. package/static/assets/ishikawaDiagram-FXEZZL3T-DF28a1N6.js +70 -0
  166. package/static/assets/journeyDiagram-5HDEW3XC-CB6CugYk.js +139 -0
  167. package/static/assets/kanban-definition-HUTT4EX6-ResSLF6d.js +89 -0
  168. package/static/assets/katex-C5jXJg4s.js +257 -0
  169. package/static/assets/layout-DEXfKzaS.js +1 -0
  170. package/static/assets/linear-CLO--ryk.js +1 -0
  171. package/static/assets/map-Czzmt4hB.js +1 -0
  172. package/static/assets/mindmap-definition-LN4V7U3C-BvW3EbxX.js +96 -0
  173. package/static/assets/ordinal-Cboi1Yqb.js +1 -0
  174. package/static/assets/pegDiagram-2B236MQR-BHCgqrrM.js +1 -0
  175. package/static/assets/pieDiagram-ENE6RG2P-DVmCQ5FA.js +39 -0
  176. package/static/assets/quadrantDiagram-ABIIQ3AL-B3e5KR52.js +7 -0
  177. package/static/assets/railroadDiagram-RFXS5EU6-Csv5ysLc.js +1 -0
  178. package/static/assets/requirementDiagram-TGXJPOKE-DOMVzvnB.js +84 -0
  179. package/static/assets/sankeyDiagram-HTMAVEWB-B1dcTOvy.js +40 -0
  180. package/static/assets/sequenceDiagram-DBY2YBRQ-BY414yeO.js +162 -0
  181. package/static/assets/sizeCapture-X5ZJPWSS-CuJfrfBN.js +1 -0
  182. package/static/assets/stateDiagram-2N3HPSRC-FX4rJkzV.js +1 -0
  183. package/static/assets/stateDiagram-v2-6OUMAXLB-BVufbO_H.js +1 -0
  184. package/static/assets/swimlanes-5IMT3BWC-Sfy58mmv.js +2 -0
  185. package/static/assets/swimlanesDiagram-G3AALYLV-wDCDw08H.js +8 -0
  186. package/static/assets/timeline-definition-FHXFAJF6-CmV7by8p.js +120 -0
  187. package/static/assets/vennDiagram-L72KCM5P-FqIyFZFt.js +34 -0
  188. package/static/assets/wardleyDiagram-EHGQE667-BZ06O45i.js +78 -0
  189. package/static/assets/xychartDiagram-FW5EYKEG-BOkffR06.js +7 -0
  190. package/static/index.html +24 -0
@@ -0,0 +1,351 @@
1
+ # Security Patterns — Keycloak OAuth2 Resource Server with Spring Security
2
+
3
+ This reference describes the complete security architecture for the REST API spec. Include
4
+ all of this content in Section 6 of the generated specification.
5
+
6
+ ---
7
+
8
+ ## Architecture Overview
9
+
10
+ The application operates as an OAuth2 Resource Server that validates JWT tokens issued by
11
+ Keycloak. Unlike the OAuth2 Client pattern (used in web apps with server-rendered views),
12
+ the Resource Server pattern is stateless — no HTTP sessions, no login pages, no cookies.
13
+
14
+ The client (e.g., a frontend SPA, mobile app, or another service) obtains a JWT access
15
+ token from Keycloak directly and includes it in the `Authorization: Bearer` header of
16
+ every API request. The REST API validates this token and extracts roles for access control.
17
+
18
+ The flow:
19
+ ```
20
+ Client → POST /realms/{realm}/protocol/openid-connect/token (to Keycloak)
21
+ → Keycloak returns JWT access token
22
+ Client → GET /api/v1/resource (with Authorization: Bearer <token>)
23
+ → Spring Security validates JWT signature via Keycloak's JWKS endpoint
24
+ → JwtAuthenticationConverter extracts roles from JWT claims
25
+ → Request proceeds to @RestController
26
+ → JSON response
27
+ ```
28
+
29
+ ### Configuration in application.yml
30
+
31
+ ```yaml
32
+ spring:
33
+ security:
34
+ oauth2:
35
+ resourceserver:
36
+ jwt:
37
+ issuer-uri: {{KEYCLOAK_ISSUER_URI}}
38
+ jwk-set-uri: {{KEYCLOAK_ISSUER_URI}}/protocol/openid-connect/certs
39
+
40
+ app:
41
+ security:
42
+ keycloak-client-id: {{KEYCLOAK_CLIENT_ID}}
43
+ public-paths:
44
+ - /api-docs/**
45
+ - /swagger-ui/**
46
+ - /swagger-ui.html
47
+ - /actuator/health
48
+ - /error
49
+ ```
50
+
51
+ ---
52
+
53
+ ## SecurityConfig.java
54
+
55
+ Complete sample code. The application uses OAuth2 Resource Server with JWT validation.
56
+ Stateless — no sessions. Keycloak provides the JWT tokens.
57
+
58
+ ```java
59
+ package {{BASE_PACKAGE}}.config;
60
+
61
+ import lombok.RequiredArgsConstructor;
62
+ import org.springframework.context.annotation.Bean;
63
+ import org.springframework.context.annotation.Configuration;
64
+ import org.springframework.http.HttpMethod;
65
+ import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
66
+ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
67
+ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
68
+ import org.springframework.security.config.http.SessionCreationPolicy;
69
+ import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
70
+ import org.springframework.security.web.SecurityFilterChain;
71
+ import org.springframework.web.cors.CorsConfigurationSource;
72
+
73
+ @Configuration
74
+ @EnableWebSecurity
75
+ @EnableMethodSecurity
76
+ @RequiredArgsConstructor
77
+ public class SecurityConfig {
78
+
79
+ private final CorsConfigurationSource corsConfigurationSource;
80
+
81
+ @Bean
82
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
83
+ http
84
+ .cors(cors -> cors.configurationSource(corsConfigurationSource))
85
+ .csrf(csrf -> csrf.disable())
86
+ .sessionManagement(session ->
87
+ session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
88
+ .authorizeHttpRequests(auth -> auth
89
+ .requestMatchers("/api-docs/**", "/swagger-ui/**",
90
+ "/swagger-ui.html", "/actuator/health", "/error").permitAll()
91
+ .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
92
+ .anyRequest().authenticated()
93
+ )
94
+ .oauth2ResourceServer(oauth2 -> oauth2
95
+ .jwt(jwt -> jwt
96
+ .jwtAuthenticationConverter(jwtAuthenticationConverter())
97
+ )
98
+ );
99
+ return http.build();
100
+ }
101
+
102
+ @Bean
103
+ public JwtAuthenticationConverter jwtAuthenticationConverter() {
104
+ JwtAuthenticationConverter converter = new JwtAuthenticationConverter();
105
+ converter.setJwtGrantedAuthoritiesConverter(new KeycloakJwtGrantedAuthoritiesConverter());
106
+ return converter;
107
+ }
108
+ }
109
+ ```
110
+
111
+ ---
112
+
113
+ ## KeycloakJwtGrantedAuthoritiesConverter.java
114
+
115
+ This class bridges Keycloak's JWT claim structure to Spring Security `GrantedAuthority`
116
+ objects. It extracts roles from both `realm_access` and `resource_access` claims in the
117
+ JWT access token.
118
+
119
+ ```java
120
+ package {{BASE_PACKAGE}}.config;
121
+
122
+ import org.springframework.beans.factory.annotation.Value;
123
+ import org.springframework.core.convert.converter.Converter;
124
+ import org.springframework.security.core.GrantedAuthority;
125
+ import org.springframework.security.core.authority.SimpleGrantedAuthority;
126
+ import org.springframework.security.oauth2.jwt.Jwt;
127
+ import org.springframework.stereotype.Component;
128
+ import java.util.ArrayList;
129
+ import java.util.Collection;
130
+ import java.util.Collections;
131
+ import java.util.List;
132
+ import java.util.Map;
133
+
134
+ @Component
135
+ public class KeycloakJwtGrantedAuthoritiesConverter
136
+ implements Converter<Jwt, Collection<GrantedAuthority>> {
137
+
138
+ @Value("${app.security.keycloak-client-id}")
139
+ private String clientId;
140
+
141
+ @Override
142
+ public Collection<GrantedAuthority> convert(Jwt jwt) {
143
+ List<GrantedAuthority> authorities = new ArrayList<>();
144
+ authorities.addAll(extractRealmRoles(jwt));
145
+ authorities.addAll(extractResourceRoles(jwt));
146
+ return authorities;
147
+ }
148
+
149
+ @SuppressWarnings("unchecked")
150
+ private Collection<GrantedAuthority> extractRealmRoles(Jwt jwt) {
151
+ Map<String, Object> realmAccess = jwt.getClaim("realm_access");
152
+ if (realmAccess == null) return Collections.emptyList();
153
+ List<String> roles = (List<String>) realmAccess.get("roles");
154
+ if (roles == null) return Collections.emptyList();
155
+ return roles.stream()
156
+ .map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role))
157
+ .toList();
158
+ }
159
+
160
+ @SuppressWarnings("unchecked")
161
+ private Collection<GrantedAuthority> extractResourceRoles(Jwt jwt) {
162
+ Map<String, Object> resourceAccess = jwt.getClaim("resource_access");
163
+ if (resourceAccess == null) return Collections.emptyList();
164
+ Map<String, Object> resource = (Map<String, Object>) resourceAccess.get(clientId);
165
+ if (resource == null) return Collections.emptyList();
166
+ List<String> roles = (List<String>) resource.get("roles");
167
+ if (roles == null) return Collections.emptyList();
168
+ return roles.stream()
169
+ .map(role -> (GrantedAuthority) new SimpleGrantedAuthority("ROLE_" + role))
170
+ .toList();
171
+ }
172
+ }
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Role-Based Access Control
178
+
179
+ ### At the endpoint level (method security)
180
+
181
+ ```java
182
+ @PreAuthorize("hasRole('ADMIN')")
183
+ @DeleteMapping("/{id}")
184
+ public ResponseEntity<Void> delete(@PathVariable String id) { ... }
185
+
186
+ @PreAuthorize("hasAnyRole('ADMIN', 'OPERATOR')")
187
+ @GetMapping
188
+ public ResponseEntity<Page<ProductDTO>> list(Pageable pageable) { ... }
189
+ ```
190
+
191
+ The `ROLE_` prefix is handled by the converter. In `@PreAuthorize`, use role names
192
+ WITHOUT the prefix (Spring Security strips it automatically in `hasRole()`).
193
+
194
+ ### Role Constants
195
+
196
+ Define roles as a constants class to avoid string duplication:
197
+
198
+ ```java
199
+ package {{BASE_PACKAGE}}.shared.security;
200
+
201
+ public final class Roles {
202
+ public static final String ADMIN = "ADMIN";
203
+ public static final String OPERATOR = "OPERATOR";
204
+ private Roles() {}
205
+ }
206
+ ```
207
+
208
+ ---
209
+
210
+ ## SecurityContextUtil.java
211
+
212
+ Utility to extract user information from the JWT principal in the current security context.
213
+
214
+ ```java
215
+ package {{BASE_PACKAGE}}.shared.security;
216
+
217
+ import org.springframework.security.core.Authentication;
218
+ import org.springframework.security.core.GrantedAuthority;
219
+ import org.springframework.security.core.context.SecurityContextHolder;
220
+ import org.springframework.security.oauth2.jwt.Jwt;
221
+ import org.springframework.stereotype.Component;
222
+ import java.util.Collections;
223
+ import java.util.Set;
224
+ import java.util.stream.Collectors;
225
+
226
+ @Component
227
+ public class SecurityContextUtil {
228
+
229
+ public String getCurrentUserId() {
230
+ Jwt jwt = getJwt();
231
+ return jwt != null ? jwt.getSubject() : null;
232
+ }
233
+
234
+ public String getCurrentUsername() {
235
+ Jwt jwt = getJwt();
236
+ return jwt != null ? jwt.getClaimAsString("preferred_username") : null;
237
+ }
238
+
239
+ public String getCurrentUserEmail() {
240
+ Jwt jwt = getJwt();
241
+ return jwt != null ? jwt.getClaimAsString("email") : null;
242
+ }
243
+
244
+ public Set<String> getCurrentRoles() {
245
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
246
+ if (auth == null) return Collections.emptySet();
247
+ return auth.getAuthorities().stream()
248
+ .map(GrantedAuthority::getAuthority)
249
+ .filter(a -> a.startsWith("ROLE_"))
250
+ .map(a -> a.substring(5))
251
+ .collect(Collectors.toSet());
252
+ }
253
+
254
+ public Jwt getJwt() {
255
+ Authentication auth = SecurityContextHolder.getContext().getAuthentication();
256
+ if (auth != null && auth.getPrincipal() instanceof Jwt jwt) {
257
+ return jwt;
258
+ }
259
+ return null;
260
+ }
261
+ }
262
+ ```
263
+
264
+ ---
265
+
266
+ ## AuditAwareImpl.java
267
+
268
+ Implements `AuditorAware<String>` to populate `@CreatedBy` and `@LastModifiedBy` fields
269
+ on JPA entities or MongoDB documents.
270
+
271
+ ```java
272
+ package {{BASE_PACKAGE}}.shared.audit;
273
+
274
+ import {{BASE_PACKAGE}}.shared.security.SecurityContextUtil;
275
+ import lombok.RequiredArgsConstructor;
276
+ import org.springframework.data.domain.AuditorAware;
277
+ import org.springframework.stereotype.Component;
278
+ import java.util.Optional;
279
+
280
+ @Component
281
+ @RequiredArgsConstructor
282
+ public class AuditAwareImpl implements AuditorAware<String> {
283
+
284
+ private final SecurityContextUtil securityContextUtil;
285
+
286
+ @Override
287
+ public Optional<String> getCurrentAuditor() {
288
+ return Optional.ofNullable(securityContextUtil.getCurrentUsername())
289
+ .or(() -> Optional.of("system"));
290
+ }
291
+ }
292
+ ```
293
+
294
+ ---
295
+
296
+ ## JWT Access Token Structure Reference
297
+
298
+ For the spec, include a sample decoded JWT access token payload showing where Keycloak
299
+ places roles:
300
+
301
+ ```json
302
+ {
303
+ "sub": "f1234-abcd-5678",
304
+ "preferred_username": "john.doe",
305
+ "email": "john.doe@example.com",
306
+ "realm_access": {
307
+ "roles": ["USER", "offline_access"]
308
+ },
309
+ "resource_access": {
310
+ "{{KEYCLOAK_CLIENT_ID}}": {
311
+ "roles": ["ADMIN"]
312
+ }
313
+ },
314
+ "scope": "openid profile email",
315
+ "iss": "{{KEYCLOAK_ISSUER_URI}}",
316
+ "exp": 1710000000,
317
+ "iat": 1709996400
318
+ }
319
+ ```
320
+
321
+ ---
322
+
323
+ ## Testing with JWT
324
+
325
+ For integration tests, mock the JWT token:
326
+
327
+ ```java
328
+ import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
329
+
330
+ @WebMvcTest(ProductController.class)
331
+ class ProductControllerTest {
332
+
333
+ @Autowired private MockMvc mockMvc;
334
+ @MockBean private ProductService productService;
335
+
336
+ @Test
337
+ void list_authenticatedUser_returns200() throws Exception {
338
+ when(productService.findAll(any(), any())).thenReturn(Page.empty());
339
+
340
+ mockMvc.perform(get("/api/v1/products")
341
+ .with(jwt().authorities(new SimpleGrantedAuthority("ROLE_ADMIN"))))
342
+ .andExpect(status().isOk());
343
+ }
344
+
345
+ @Test
346
+ void list_unauthenticated_returns401() throws Exception {
347
+ mockMvc.perform(get("/api/v1/products"))
348
+ .andExpect(status().isUnauthorized());
349
+ }
350
+ }
351
+ ```