@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,501 @@
1
+ # Security Patterns — Keycloak OAuth2 Client with Laravel Socialite
2
+
3
+ This reference describes the complete security architecture for the spec. Include all of
4
+ this content in Section 6 of the generated specification.
5
+
6
+ ---
7
+
8
+ ## Architecture Overview
9
+
10
+ The application operates as an OAuth2 Client using Authorization Code flow with OIDC.
11
+ Keycloak is the external identity provider that handles login UI, user registration,
12
+ and role management. The application's responsibilities are:
13
+
14
+ 1. Redirect unauthenticated users to Keycloak login page
15
+ 2. Handle the OAuth2 callback and establish an HTTP session
16
+ 3. Extract roles from OIDC ID token claims
17
+ 4. Enforce role-based access control on routes
18
+
19
+ The application uses server-side sessions (Laravel's default). After successful OAuth2
20
+ login, the authenticated user is stored in the session. User identity and roles are
21
+ read from the OIDC ID token claims.
22
+
23
+ The flow:
24
+ ```
25
+ Browser → GET /protected-page → Auth Middleware (not authenticated)
26
+ → 302 Redirect to Keycloak /auth?response_type=code&client_id=...
27
+ → User logs in at Keycloak
28
+ → Keycloak redirects back with authorization code
29
+ → Laravel exchanges code for tokens (ID token + access token) via Socialite
30
+ → KeycloakRoleMapper extracts roles from ID token
31
+ → Session created, User stored as authenticated principal
32
+ → 302 Redirect to original page
33
+ ```
34
+
35
+ ### Configuration in .env
36
+
37
+ ```env
38
+ KEYCLOAK_CLIENT_ID={{KEYCLOAK_CLIENT_ID}}
39
+ KEYCLOAK_CLIENT_SECRET={{KEYCLOAK_CLIENT_SECRET}}
40
+ KEYCLOAK_REALM={{KEYCLOAK_REALM}}
41
+ KEYCLOAK_BASE_URL={{KEYCLOAK_BASE_URL}}
42
+ KEYCLOAK_REDIRECT_URI=${APP_URL}/auth/callback
43
+ ```
44
+
45
+ ### Configuration in config/services.php
46
+
47
+ ```php
48
+ 'keycloak' => [
49
+ 'client_id' => env('KEYCLOAK_CLIENT_ID'),
50
+ 'client_secret' => env('KEYCLOAK_CLIENT_SECRET'),
51
+ 'redirect' => env('KEYCLOAK_REDIRECT_URI'),
52
+ 'base_url' => env('KEYCLOAK_BASE_URL'),
53
+ 'realm' => env('KEYCLOAK_REALM'),
54
+ ],
55
+ ```
56
+
57
+ ---
58
+
59
+ ## Socialite Keycloak Provider Setup
60
+
61
+ Install and configure the Keycloak Socialite provider:
62
+
63
+ ```php
64
+ // config/app.php or AppServiceProvider boot()
65
+ // The socialiteproviders/keycloak package auto-registers via EventServiceProvider
66
+
67
+ // In app/Providers/EventServiceProvider.php
68
+ protected $listen = [
69
+ \SocialiteProviders\Manager\SocialiteWasCalled::class => [
70
+ \SocialiteProviders\Keycloak\KeycloakExtendSocialite::class . '@handle',
71
+ ],
72
+ ];
73
+ ```
74
+
75
+ ---
76
+
77
+ ## Auth Controller
78
+
79
+ Complete controller handling OAuth2 login flow with Keycloak:
80
+
81
+ ```php
82
+ namespace App\Http\Controllers\Auth;
83
+
84
+ use App\Http\Controllers\Controller;
85
+ use App\Models\User;
86
+ use App\Services\KeycloakRoleMapper;
87
+ use Illuminate\Http\RedirectResponse;
88
+ use Illuminate\Support\Facades\Auth;
89
+ use Laravel\Socialite\Facades\Socialite;
90
+
91
+ class KeycloakAuthController extends Controller
92
+ {
93
+ public function __construct(
94
+ private readonly KeycloakRoleMapper $roleMapper,
95
+ ) {}
96
+
97
+ public function redirect(): RedirectResponse
98
+ {
99
+ return Socialite::driver('keycloak')
100
+ ->scopes(['openid', 'profile', 'email'])
101
+ ->redirect();
102
+ }
103
+
104
+ public function callback(): RedirectResponse
105
+ {
106
+ $keycloakUser = Socialite::driver('keycloak')->user();
107
+
108
+ $user = User::updateOrCreate(
109
+ ['keycloak_id' => $keycloakUser->getId()],
110
+ [
111
+ 'name' => $keycloakUser->getName(),
112
+ 'email' => $keycloakUser->getEmail(),
113
+ 'username' => $keycloakUser->getNickname(),
114
+ ]
115
+ );
116
+
117
+ // Sync roles from Keycloak token
118
+ $roles = $this->roleMapper->extractRoles($keycloakUser->accessTokenResponseBody);
119
+ $user->syncRoles($roles);
120
+
121
+ Auth::login($user, remember: true);
122
+
123
+ return redirect()->intended('/');
124
+ }
125
+
126
+ public function logout(): RedirectResponse
127
+ {
128
+ Auth::logout();
129
+ request()->session()->invalidate();
130
+ request()->session()->regenerateToken();
131
+
132
+ // Redirect to Keycloak logout endpoint
133
+ $keycloakLogoutUrl = config('services.keycloak.base_url')
134
+ . '/realms/' . config('services.keycloak.realm')
135
+ . '/protocol/openid-connect/logout'
136
+ . '?redirect_uri=' . urlencode(config('app.url'));
137
+
138
+ return redirect($keycloakLogoutUrl);
139
+ }
140
+ }
141
+ ```
142
+
143
+ ---
144
+
145
+ ## KeycloakRoleMapper
146
+
147
+ This class bridges Keycloak's OIDC token claim structure to spatie/laravel-permission
148
+ roles. It extracts roles from both `realm_access` and `resource_access` claims in the
149
+ token response.
150
+
151
+ ```php
152
+ namespace App\Services;
153
+
154
+ use Illuminate\Support\Arr;
155
+
156
+ class KeycloakRoleMapper
157
+ {
158
+ public function __construct(
159
+ private readonly string $clientId,
160
+ ) {}
161
+
162
+ public static function make(): self
163
+ {
164
+ return new self(config('services.keycloak.client_id'));
165
+ }
166
+
167
+ /**
168
+ * Extract roles from the Keycloak token response body.
169
+ *
170
+ * @param array $tokenResponse The full token response including 'id_token' claims
171
+ * @return array<string> List of role names (e.g., ['HUB_ADMINISTRATOR', 'USER'])
172
+ */
173
+ public function extractRoles(array $tokenResponse): array
174
+ {
175
+ $roles = [];
176
+
177
+ // Decode the ID token to get claims
178
+ $idToken = $tokenResponse['id_token'] ?? null;
179
+ if ($idToken) {
180
+ $claims = json_decode(
181
+ base64_decode(explode('.', $idToken)[1]),
182
+ associative: true
183
+ );
184
+
185
+ // Extract realm roles
186
+ $realmRoles = Arr::get($claims, 'realm_access.roles', []);
187
+ $roles = array_merge($roles, $realmRoles);
188
+
189
+ // Extract client-specific roles
190
+ $clientRoles = Arr::get(
191
+ $claims,
192
+ "resource_access.{$this->clientId}.roles",
193
+ []
194
+ );
195
+ $roles = array_merge($roles, $clientRoles);
196
+ }
197
+
198
+ // Normalize: uppercase, unique
199
+ return array_unique(array_map('strtoupper', $roles));
200
+ }
201
+ }
202
+ ```
203
+
204
+ ---
205
+
206
+ ## Route Configuration
207
+
208
+ ```php
209
+ // routes/web.php
210
+
211
+ use App\Http\Controllers\Auth\KeycloakAuthController;
212
+
213
+ // Public routes
214
+ Route::get('/auth/redirect', [KeycloakAuthController::class, 'redirect'])
215
+ ->name('auth.redirect');
216
+ Route::get('/auth/callback', [KeycloakAuthController::class, 'callback'])
217
+ ->name('auth.callback');
218
+
219
+ // Authenticated routes
220
+ Route::middleware(['auth', 'verified'])->group(function () {
221
+ Route::post('/logout', [KeycloakAuthController::class, 'logout'])
222
+ ->name('logout');
223
+
224
+ // Module routes loaded from modules...
225
+ });
226
+ ```
227
+
228
+ ---
229
+
230
+ ## Role-Based Access Control
231
+
232
+ ### Using spatie/laravel-permission middleware
233
+
234
+ ```php
235
+ // In module route files
236
+ Route::middleware(['role:HUB_ADMINISTRATOR'])->group(function () {
237
+ Route::get('/corridor', [CorridorPageController::class, 'index']);
238
+ Route::get('/corridor/{id}', [CorridorPageController::class, 'show']);
239
+ });
240
+
241
+ Route::middleware(['role:HUB_ADMINISTRATOR|HUB_OPERATION_SUPPORT'])->group(function () {
242
+ Route::get('/employer', [EmployerPageController::class, 'index']);
243
+ });
244
+ ```
245
+
246
+ ### In Blade templates
247
+
248
+ ```blade
249
+ @role('HUB_ADMINISTRATOR')
250
+ <a href="/corridor">Corridor Management</a>
251
+ @endrole
252
+
253
+ @hasanyrole('HUB_ADMINISTRATOR|HUB_OPERATION_SUPPORT')
254
+ <a href="/employer">Employers</a>
255
+ @endhasanyrole
256
+ ```
257
+
258
+ ### In controllers (Gate authorization)
259
+
260
+ ```php
261
+ use Illuminate\Support\Facades\Gate;
262
+
263
+ public function destroy(string $id)
264
+ {
265
+ Gate::authorize('delete-order');
266
+ $this->orderService->delete($id);
267
+ return redirect()->route('order.index');
268
+ }
269
+ ```
270
+
271
+ ### Role constants
272
+
273
+ Define roles as a constants class to avoid string duplication:
274
+
275
+ ```php
276
+ namespace App\Constants;
277
+
278
+ final class Roles
279
+ {
280
+ public const HUB_ADMINISTRATOR = 'HUB_ADMINISTRATOR';
281
+ public const HUB_OPERATION_SUPPORT = 'HUB_OPERATION_SUPPORT';
282
+
283
+ private function __construct() {}
284
+ }
285
+ ```
286
+
287
+ ---
288
+
289
+ ## SecurityContext Helper
290
+
291
+ Utility to extract user information from the authenticated session. Wraps Laravel's
292
+ `auth()` helper for consistent access across services.
293
+
294
+ ```php
295
+ namespace App\Services;
296
+
297
+ class SecurityContext
298
+ {
299
+ public function getCurrentUserId(): ?string
300
+ {
301
+ return auth()->id();
302
+ }
303
+
304
+ public function getCurrentUsername(): ?string
305
+ {
306
+ return auth()->user()?->username;
307
+ }
308
+
309
+ public function getCurrentUserEmail(): ?string
310
+ {
311
+ return auth()->user()?->email;
312
+ }
313
+
314
+ public function getCurrentRoles(): array
315
+ {
316
+ $user = auth()->user();
317
+ return $user ? $user->getRoleNames()->toArray() : [];
318
+ }
319
+
320
+ public function hasRole(string $role): bool
321
+ {
322
+ return auth()->user()?->hasRole($role) ?? false;
323
+ }
324
+ }
325
+ ```
326
+
327
+ This class is injected via constructor into:
328
+ - `BlameableTrait` — for `created_by` / `updated_by` fields
329
+ - Service classes — when business logic depends on the current user
330
+ - Correlation ID middleware — for logging
331
+
332
+ ---
333
+
334
+ ## BlameableTrait
335
+
336
+ Trait for Eloquent models to automatically populate `created_by` and `updated_by` fields.
337
+
338
+ ```php
339
+ namespace App\Traits;
340
+
341
+ trait Blameable
342
+ {
343
+ public static function bootBlameable(): void
344
+ {
345
+ static::creating(function ($model) {
346
+ if (auth()->check()) {
347
+ $model->created_by = auth()->user()->username ?? auth()->id();
348
+ $model->updated_by = auth()->user()->username ?? auth()->id();
349
+ } else {
350
+ $model->created_by = 'system';
351
+ $model->updated_by = 'system';
352
+ }
353
+ });
354
+
355
+ static::updating(function ($model) {
356
+ $model->updated_by = auth()->check()
357
+ ? (auth()->user()->username ?? auth()->id())
358
+ : 'system';
359
+ });
360
+ }
361
+ }
362
+ ```
363
+
364
+ Usage in models:
365
+ ```php
366
+ class Order extends Model
367
+ {
368
+ use Blameable;
369
+
370
+ // created_by and updated_by are auto-populated
371
+ }
372
+ ```
373
+
374
+ ---
375
+
376
+ ## OIDC ID Token Structure Reference
377
+
378
+ For the spec, include a sample decoded ID token payload showing where Keycloak places
379
+ roles, so developers understand the claim structure:
380
+
381
+ ```json
382
+ {
383
+ "sub": "f1234-abcd-5678",
384
+ "preferred_username": "john.doe",
385
+ "email": "john.doe@example.com",
386
+ "realm_access": {
387
+ "roles": ["USER", "offline_access"]
388
+ },
389
+ "resource_access": {
390
+ "{{KEYCLOAK_CLIENT_ID}}": {
391
+ "roles": ["HUB_ADMINISTRATOR"]
392
+ }
393
+ },
394
+ "scope": "openid profile email"
395
+ }
396
+ ```
397
+
398
+ This helps developers understand exactly what `KeycloakRoleMapper` is parsing.
399
+
400
+ ---
401
+
402
+ ## CSP Nonce Middleware
403
+
404
+ Content Security Policy nonce generated per request to allow inline scripts while
405
+ maintaining CSP security.
406
+
407
+ ```php
408
+ namespace App\Http\Middleware;
409
+
410
+ use Closure;
411
+ use Illuminate\Http\Request;
412
+ use Illuminate\Support\Facades\Vite;
413
+ use Illuminate\Support\Str;
414
+ use Symfony\Component\HttpFoundation\Response;
415
+
416
+ class CspNonceMiddleware
417
+ {
418
+ public function handle(Request $request, Closure $next): Response
419
+ {
420
+ $nonce = Str::random(32);
421
+
422
+ // Share nonce with Vite for script tags
423
+ Vite::useCspNonce($nonce);
424
+
425
+ // Store nonce for Blade templates
426
+ view()->share('cspNonce', $nonce);
427
+
428
+ $response = $next($request);
429
+
430
+ $response->headers->set('Content-Security-Policy',
431
+ "default-src 'self'; " .
432
+ "script-src 'self' 'nonce-{$nonce}'; " .
433
+ "style-src 'self' 'unsafe-inline';"
434
+ );
435
+
436
+ return $response;
437
+ }
438
+ }
439
+ ```
440
+
441
+ Register in `bootstrap/app.php`:
442
+ ```php
443
+ ->withMiddleware(function (Middleware $middleware) {
444
+ $middleware->web(append: [
445
+ \App\Http\Middleware\CspNonceMiddleware::class,
446
+ ]);
447
+ })
448
+ ```
449
+
450
+ ---
451
+
452
+ ## ThemeMiddleware
453
+
454
+ Middleware to read the theme cookie and share with all views.
455
+
456
+ ```php
457
+ namespace App\Http\Middleware;
458
+
459
+ use Closure;
460
+ use Illuminate\Http\Request;
461
+ use Symfony\Component\HttpFoundation\Response;
462
+
463
+ class ThemeMiddleware
464
+ {
465
+ public function handle(Request $request, Closure $next): Response
466
+ {
467
+ $theme = $request->cookie('theme', 'light');
468
+ view()->share('theme', $theme);
469
+
470
+ return $next($request);
471
+ }
472
+ }
473
+ ```
474
+
475
+ ---
476
+
477
+ ## User Model for Keycloak
478
+
479
+ ```php
480
+ namespace App\Models;
481
+
482
+ use App\Traits\Blameable;
483
+ use Illuminate\Foundation\Auth\User as Authenticatable;
484
+ use Spatie\Permission\Traits\HasRoles;
485
+
486
+ class User extends Authenticatable
487
+ {
488
+ use HasRoles, Blameable;
489
+
490
+ protected $fillable = [
491
+ 'keycloak_id',
492
+ 'name',
493
+ 'email',
494
+ 'username',
495
+ ];
496
+
497
+ protected $hidden = [
498
+ 'remember_token',
499
+ ];
500
+ }
501
+ ```