funifier-mcp 0.2.25 → 0.2.27

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 (211) hide show
  1. package/.cursor/rules/funifier.mdc +38 -41
  2. package/.github/copilot-instructions.md +38 -41
  3. package/AGENTS.md +56 -49
  4. package/README.md +40 -22
  5. package/datasource-funifier-docs/.coverage.json +326 -0
  6. package/datasource-funifier-docs/.validation.json +593 -0
  7. package/datasource-funifier-docs/knowledge/guides/aggregates.md +182 -70
  8. package/datasource-funifier-docs/knowledge/guides/database-access.md +174 -88
  9. package/datasource-funifier-docs/knowledge/guides/java-entities.md +294 -204
  10. package/datasource-funifier-docs/knowledge/guides/java-libraries.md +202 -226
  11. package/datasource-funifier-docs/knowledge/guides/java-managers.md +343 -265
  12. package/datasource-funifier-docs/knowledge/guides/trigger-examples.md +180 -236
  13. package/datasource-funifier-docs/knowledge/guides/triggers-guide.md +273 -191
  14. package/datasource-funifier-docs/knowledge/index.md +5 -2
  15. package/datasource-funifier-docs/knowledge/modules/achievement.md +1126 -28
  16. package/datasource-funifier-docs/knowledge/modules/action-log.md +469 -62
  17. package/datasource-funifier-docs/knowledge/modules/action.md +522 -70
  18. package/datasource-funifier-docs/knowledge/modules/auth.md +718 -69
  19. package/datasource-funifier-docs/knowledge/modules/avatar.md +483 -18
  20. package/datasource-funifier-docs/knowledge/modules/backup.md +603 -25
  21. package/datasource-funifier-docs/knowledge/modules/challenge.md +1048 -220
  22. package/datasource-funifier-docs/knowledge/modules/compact.md +469 -26
  23. package/datasource-funifier-docs/knowledge/modules/competition.md +811 -109
  24. package/datasource-funifier-docs/knowledge/modules/crossword.md +504 -28
  25. package/datasource-funifier-docs/knowledge/modules/csv-data.md +645 -20
  26. package/datasource-funifier-docs/knowledge/modules/custom-object.md +701 -36
  27. package/datasource-funifier-docs/knowledge/modules/database.md +730 -164
  28. package/datasource-funifier-docs/knowledge/modules/folder.md +1011 -77
  29. package/datasource-funifier-docs/knowledge/modules/kpi-formulas.md +410 -15
  30. package/datasource-funifier-docs/knowledge/modules/lastmile.md +568 -29
  31. package/datasource-funifier-docs/knowledge/modules/leaderboard.md +595 -126
  32. package/datasource-funifier-docs/knowledge/modules/level.md +536 -54
  33. package/datasource-funifier-docs/knowledge/modules/lottery.md +809 -76
  34. package/datasource-funifier-docs/knowledge/modules/marketplace.md +688 -17
  35. package/datasource-funifier-docs/knowledge/modules/mystery.md +662 -52
  36. package/datasource-funifier-docs/knowledge/modules/notification.md +564 -26
  37. package/datasource-funifier-docs/knowledge/modules/patterns.md +519 -814
  38. package/datasource-funifier-docs/knowledge/modules/player.md +773 -73
  39. package/datasource-funifier-docs/knowledge/modules/point.md +380 -83
  40. package/datasource-funifier-docs/knowledge/modules/public.md +508 -178
  41. package/datasource-funifier-docs/knowledge/modules/question.md +619 -99
  42. package/datasource-funifier-docs/knowledge/modules/quiz.md +565 -120
  43. package/datasource-funifier-docs/knowledge/modules/scheduler.md +1092 -39
  44. package/datasource-funifier-docs/knowledge/modules/security.md +674 -112
  45. package/datasource-funifier-docs/knowledge/modules/staging.md +742 -19
  46. package/datasource-funifier-docs/knowledge/modules/story.md +565 -29
  47. package/datasource-funifier-docs/knowledge/modules/studio-page.md +470 -144
  48. package/datasource-funifier-docs/knowledge/modules/swap.md +552 -84
  49. package/datasource-funifier-docs/knowledge/modules/team.md +563 -45
  50. package/datasource-funifier-docs/knowledge/modules/trigger.md +876 -134
  51. package/datasource-funifier-docs/knowledge/modules/upload.md +468 -95
  52. package/datasource-funifier-docs/knowledge/modules/virtual-good.md +510 -63
  53. package/datasource-funifier-docs/knowledge/modules/webhook.md +375 -28
  54. package/datasource-funifier-docs/knowledge/modules/websocket.md +459 -26
  55. package/datasource-funifier-docs/knowledge/modules/widget.md +613 -27
  56. package/dist/cli/init.d.ts.map +1 -1
  57. package/dist/cli/init.js +42 -1
  58. package/dist/cli/init.js.map +1 -1
  59. package/dist/cli/init.test.js +74 -3
  60. package/dist/cli/init.test.js.map +1 -1
  61. package/dist/cli/persona.d.ts +3 -0
  62. package/dist/cli/persona.d.ts.map +1 -0
  63. package/dist/cli/persona.js +25 -0
  64. package/dist/cli/persona.js.map +1 -0
  65. package/dist/core/api-client.d.ts +21 -1
  66. package/dist/core/api-client.d.ts.map +1 -1
  67. package/dist/core/api-client.js +154 -1
  68. package/dist/core/api-client.js.map +1 -1
  69. package/dist/core/constants.d.ts +14 -0
  70. package/dist/core/constants.d.ts.map +1 -1
  71. package/dist/core/constants.js +14 -0
  72. package/dist/core/constants.js.map +1 -1
  73. package/dist/core/types/Folder.d.ts +16 -0
  74. package/dist/core/types/Folder.d.ts.map +1 -0
  75. package/dist/core/types/Folder.js +3 -0
  76. package/dist/core/types/Folder.js.map +1 -0
  77. package/dist/core/types/FolderContent.d.ts +10 -0
  78. package/dist/core/types/FolderContent.d.ts.map +1 -0
  79. package/dist/core/types/FolderContent.js +3 -0
  80. package/dist/core/types/FolderContent.js.map +1 -0
  81. package/dist/core/types/FolderContentType.d.ts +10 -0
  82. package/dist/core/types/FolderContentType.d.ts.map +1 -0
  83. package/dist/core/types/FolderContentType.js +3 -0
  84. package/dist/core/types/FolderContentType.js.map +1 -0
  85. package/dist/core/types/FolderLog.d.ts +11 -0
  86. package/dist/core/types/FolderLog.d.ts.map +1 -0
  87. package/dist/core/types/FolderLog.js +3 -0
  88. package/dist/core/types/FolderLog.js.map +1 -0
  89. package/dist/core/types/index.d.ts +4 -0
  90. package/dist/core/types/index.d.ts.map +1 -1
  91. package/dist/core/types/index.js +4 -0
  92. package/dist/core/types/index.js.map +1 -1
  93. package/dist/mcp/bundle.js +121 -87
  94. package/dist/mcp/check-update.d.ts +2 -0
  95. package/dist/mcp/check-update.d.ts.map +1 -0
  96. package/dist/mcp/check-update.js +44 -0
  97. package/dist/mcp/check-update.js.map +1 -0
  98. package/dist/mcp/index.js +5 -2
  99. package/dist/mcp/index.js.map +1 -1
  100. package/dist/mcp/resources/documentation.d.ts +1 -1
  101. package/dist/mcp/resources/documentation.d.ts.map +1 -1
  102. package/dist/mcp/resources/documentation.js +39 -3
  103. package/dist/mcp/resources/documentation.js.map +1 -1
  104. package/dist/mcp/tools/_char-guard.js +1 -1
  105. package/dist/mcp/tools/_char-guard.js.map +1 -1
  106. package/dist/mcp/tools/_fetch-current.d.ts +1 -1
  107. package/dist/mcp/tools/_fetch-current.d.ts.map +1 -1
  108. package/dist/mcp/tools/_fetch-current.js +12 -0
  109. package/dist/mcp/tools/_fetch-current.js.map +1 -1
  110. package/dist/mcp/tools/connect.d.ts.map +1 -1
  111. package/dist/mcp/tools/connect.js +18 -8
  112. package/dist/mcp/tools/connect.js.map +1 -1
  113. package/dist/mcp/tools/database.d.ts.map +1 -1
  114. package/dist/mcp/tools/database.js +59 -47
  115. package/dist/mcp/tools/database.js.map +1 -1
  116. package/dist/mcp/tools/database.test.js +2 -2
  117. package/dist/mcp/tools/database.test.js.map +1 -1
  118. package/dist/mcp/tools/delete.d.ts.map +1 -1
  119. package/dist/mcp/tools/delete.js +33 -3
  120. package/dist/mcp/tools/delete.js.map +1 -1
  121. package/dist/mcp/tools/execute.d.ts.map +1 -1
  122. package/dist/mcp/tools/execute.js +20 -9
  123. package/dist/mcp/tools/execute.js.map +1 -1
  124. package/dist/mcp/tools/folder.d.ts +4 -0
  125. package/dist/mcp/tools/folder.d.ts.map +1 -0
  126. package/dist/mcp/tools/folder.js +68 -0
  127. package/dist/mcp/tools/folder.js.map +1 -0
  128. package/dist/mcp/tools/get.d.ts.map +1 -1
  129. package/dist/mcp/tools/get.js +16 -6
  130. package/dist/mcp/tools/get.js.map +1 -1
  131. package/dist/mcp/tools/index.d.ts +1 -1
  132. package/dist/mcp/tools/index.d.ts.map +1 -1
  133. package/dist/mcp/tools/index.js +5 -1
  134. package/dist/mcp/tools/index.js.map +1 -1
  135. package/dist/mcp/tools/list.d.ts.map +1 -1
  136. package/dist/mcp/tools/list.js +38 -14
  137. package/dist/mcp/tools/list.js.map +1 -1
  138. package/dist/mcp/tools/logs.d.ts.map +1 -1
  139. package/dist/mcp/tools/logs.js +15 -5
  140. package/dist/mcp/tools/logs.js.map +1 -1
  141. package/dist/mcp/tools/save.d.ts.map +1 -1
  142. package/dist/mcp/tools/save.js +26 -4
  143. package/dist/mcp/tools/save.js.map +1 -1
  144. package/dist/mcp/tools/save.test.js +192 -1
  145. package/dist/mcp/tools/save.test.js.map +1 -1
  146. package/dist/mcp/tools/search-docs.d.ts +3 -0
  147. package/dist/mcp/tools/search-docs.d.ts.map +1 -0
  148. package/dist/mcp/tools/search-docs.js +102 -0
  149. package/dist/mcp/tools/search-docs.js.map +1 -0
  150. package/package.json +6 -2
  151. package/skills/acquire-funifier-knowledge/SKILL.md +132 -0
  152. package/skills/acquire-funifier-knowledge/assets/templates/CONCERNS.md +25 -0
  153. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_ENDPOINTS.md +24 -0
  154. package/skills/acquire-funifier-knowledge/assets/templates/CUSTOM_PAGES.md +24 -0
  155. package/skills/acquire-funifier-knowledge/assets/templates/GAME_MECHANICS.md +35 -0
  156. package/skills/acquire-funifier-knowledge/assets/templates/INTEGRATIONS.md +35 -0
  157. package/skills/acquire-funifier-knowledge/assets/templates/LEADERBOARDS.md +24 -0
  158. package/skills/acquire-funifier-knowledge/assets/templates/OVERVIEW.md +47 -0
  159. package/skills/acquire-funifier-knowledge/assets/templates/PLAYER_MODEL.md +31 -0
  160. package/skills/acquire-funifier-knowledge/assets/templates/SCHEDULERS.md +25 -0
  161. package/skills/acquire-funifier-knowledge/assets/templates/TECHNIQUES_AND_PATTERNS.md +26 -0
  162. package/skills/acquire-funifier-knowledge/assets/templates/TRIGGERS.md +27 -0
  163. package/skills/acquire-funifier-knowledge/references/funifier-inventory-checklist.md +81 -0
  164. package/skills/acquire-funifier-knowledge/references/game-techniques-taxonomy.md +62 -0
  165. package/skills/acquire-funifier-knowledge/references/mcp-call-patterns.md +118 -0
  166. package/skills/funifier/SKILL.md +88 -0
  167. package/skills/funifier/references/configure-security.md +96 -0
  168. package/skills/{funifier-create-action/SKILL.md → funifier/references/create-action.md} +0 -33
  169. package/skills/funifier/references/create-aggregate.md +144 -0
  170. package/skills/funifier/references/create-challenge.md +116 -0
  171. package/skills/funifier/references/create-competition.md +98 -0
  172. package/skills/funifier/references/create-crossword.md +574 -0
  173. package/skills/funifier/references/create-custom-object.md +91 -0
  174. package/skills/funifier/references/create-custom-page.md +135 -0
  175. package/skills/funifier/references/create-folder.md +104 -0
  176. package/skills/funifier/references/create-lastmile.md +643 -0
  177. package/skills/{funifier-create-leaderboard/SKILL.md → funifier/references/create-leaderboard.md} +0 -33
  178. package/skills/funifier/references/create-level.md +94 -0
  179. package/skills/funifier/references/create-lottery.md +913 -0
  180. package/skills/funifier/references/create-mystery.md +769 -0
  181. package/skills/funifier/references/create-notification.md +75 -0
  182. package/skills/{funifier-create-point/SKILL.md → funifier/references/create-point.md} +0 -33
  183. package/skills/funifier/references/create-quiz.md +98 -0
  184. package/skills/funifier/references/create-scheduler.md +141 -0
  185. package/skills/funifier/references/create-story.md +636 -0
  186. package/skills/funifier/references/create-swap.md +95 -0
  187. package/skills/{funifier-create-trigger/SKILL.md → funifier/references/create-trigger.md} +0 -33
  188. package/skills/funifier/references/create-virtual-good.md +96 -0
  189. package/skills/funifier/references/create-webhook.md +72 -0
  190. package/skills/funifier/references/create-websocket.md +71 -0
  191. package/skills/funifier/references/create-widget.md +76 -0
  192. package/skills/funifier/references/debug.md +87 -0
  193. package/skills/funifier/references/help.md +81 -0
  194. package/skills/funifier/references/implement-frontend.md +106 -0
  195. package/skills/funifier/references/import-csv.md +75 -0
  196. package/skills/funifier/references/manage-player.md +82 -0
  197. package/skills/funifier/references/manage-team.md +76 -0
  198. package/skills/funifier/references/upload-file.md +91 -0
  199. package/datasource-funifier-docs/.search-index.json +0 -17318
  200. package/datasource-funifier-docs/.skills-map.json +0 -73
  201. package/skills/funifier-create-aggregate/SKILL.md +0 -127
  202. package/skills/funifier-create-challenge/SKILL.md +0 -88
  203. package/skills/funifier-create-custom-page/SKILL.md +0 -127
  204. package/skills/funifier-create-level/SKILL.md +0 -87
  205. package/skills/funifier-create-quiz/SKILL.md +0 -87
  206. package/skills/funifier-create-scheduler/SKILL.md +0 -127
  207. package/skills/funifier-create-virtual-good/SKILL.md +0 -87
  208. package/skills/funifier-debug/SKILL.md +0 -92
  209. package/skills/funifier-help/SKILL.md +0 -86
  210. package/skills/funifier-implement-frontend/SKILL.md +0 -90
  211. package/skills/funifier-index/SKILL.md +0 -58
@@ -1,104 +1,753 @@
1
- # Auth (Autenticação)
1
+ # `auth`
2
2
 
3
- **Acesso Studio:** `/studio/auth`
3
+ **Acesso Studio:** `/studio/security` _(rota de frontend — não verificável neste repositório)_
4
4
  **API Endpoint:** `/v3/auth`
5
+ **Coleções MongoDB:** `auth_module` (gerenciada por este endpoint), `authentication` (legada v1/v2). Lê — sem gravar — `security` e `principal`.
5
6
 
6
- ## O que é
7
+ > Documentação de engenharia reversa do módulo `auth` do projeto `funifier-service`. Baseada exclusivamente no comportamento real do código-fonte (pacotes `com.funifier.engine.auth.*`, `com.funifier.rest.v3.*`). Cada comportamento não óbvio cita `arquivo:linha`.
7
8
 
8
- Gerenciamento de autenticação e critérios básicos de segurança. Permite configurar como será o processo de login dos jogadores, definindo exigência de senha, auto-criação de usuários, geração de tokens de acesso (incluindo tempo de expiração e revalidação). Também permite criar rotinas personalizadas de autenticação (Auth Module) para SSO, validação externa e logs de login.
9
+ ---
9
10
 
10
- ## Quando usar
11
+ ## 1. Visão Geral
11
12
 
12
- - Para configurar login de jogadores
13
- - Para gerar tokens de acesso
14
- - Para implementar SSO com sistemas corporativos
15
- - Para criar validações customizadas de autenticação
13
+ O módulo `auth` tem duas responsabilidades distintas, ambas servidas pela classe `AuthenticationRest` (`@Path("v3/auth")`, `rest/v3/rest/AuthenticationRest.java:52`):
16
14
 
17
- ## Checklist de Configuração no Studio
15
+ 1. **Emissão de tokens** (`POST /v3/auth/token`): implementação parcial de OAuth 2.0 (RFC6749) que autentica jogadores/aplicações e emite JWTs Bearer. Suporta `client_credentials`, `password` e `refresh_token`.
16
+ 2. **CRUD de Auth Modules** (`/v3/auth/module`): cadastro de scripts Groovy customizados que rodam no login do jogador (PAM stacking — login por CPF, SSO externo, senha mestre, etc.).
18
17
 
19
- - [ ] Definir se senha é obrigatória
20
- - [ ] Configurar auto-criação de usuários
21
- - [ ] Definir tempo de expiração do token
22
- - [ ] Configurar módulo de autenticação customizado (se necessário)
18
+ A **autorização** de todas as requisições `/v3/*` acontece no `SecurityFilter` (`rest/v3/filter/SecurityFilter.java:27`), um servlet filter que valida o token e o escopo antes de liberar o acesso. O `SecurityFilter` é descrito aqui porque é o consumidor em runtime dos tokens emitidos pelo `/v3/auth/token`.
23
19
 
24
- ## API Endpoints
20
+ A lógica de autenticação de jogador vive em `com.funifier.engine.auth.AuthenticationManager` (`engine/auth/AuthenticationManager.java:37`), instanciada por gamificação via `FrontController.getInstance(apiKey).getManagerFactory()`.
25
21
 
26
- ### Autenticar Jogador
27
- **Método:** POST
28
- **Endpoint:** `/v3/auth/token`
22
+ > **Escopo deste documento:** cobre exclusivamente a autenticação de **jogadores** (`/v3/auth`). Subsistemas correlatos — `/v3/system/auth/module` (auth modules de usuário Studio), `/v3/sso/saml/*` (SSO de administradores Studio) e 2FA (usuários de conta) — são explicitamente delimitados na seção 7.
23
+
24
+ ---
25
+
26
+ ## 2. Arquitetura e Fluxos
27
+
28
+ ### 2.1 Pipeline principal — `POST /v3/auth/token`
29
+
30
+ O mesmo path aceita dois `Content-Type` em métodos diferentes:
31
+
32
+ - `application/x-www-form-urlencoded` → `AuthenticationRest.token()` (`:88`)
33
+ - `application/json` → `AuthenticationRest.tokenJson()` (`:232`)
34
+
35
+ Os dois métodos **não são equivalentes** (ver 2.4). Sequência do `token()` (form):
36
+
37
+ ```
38
+ POST /v3/auth/token
39
+ 1. [Roteamento por grant_type] → request.getParameter("grant_type") token():98
40
+ 2a. client_credentials:
41
+ isAppSecretAllowed(client_secret) AuthenticationManager:189
42
+ se inválido → 401 UNAUTHORIZED_CLIENT token():108
43
+ findApplication(secret) → app.scope, app.whitelist token():111-112
44
+ accessToken(scale=DAY, amount=7) + refreshToken (1 ano) token():112-113
45
+ 2b. password:
46
+ authenticateModules(req, m, context) token():133 → AuthenticationManager:693
47
+ moduleStatus==2 → 401 auth_module_failed (se houver output) ou 400 invalid_grant token():135-144
48
+ username = req.getParameter("username") ← pode ter sido reescrito por módulo token():148
49
+ moduleStatus==1 OU (moduleStatus==0 && authenticatePassword(player,password)) token():150
50
+ findRoleCalculatedToPlayer(player) → scope, expiration, whitelist token():151-155
51
+ accessToken(scope, expiration, role.whitelist) + refreshToken token():155-156
52
+ 2c. refresh_token:
53
+ accessTokenWithRefreshToken(refresh_token) token():171 → TokenUtil:80
54
+ gera novo refresh_token (rotativo) token():181
55
+ 2d. qualquer outro grant_type → 400 invalid_grant token():187
56
+ ```
57
+
58
+ **Algoritmo de decisão dos Auth Modules** (`AuthenticationManager.authenticateModules`, `:693`):
59
+
60
+ ```
61
+ status = 0; ok_sufficient = 0; ok_required = 0; tot_required = 0
62
+ para cada AuthModule ordenado por "order" asc (findAll():685):
63
+ se relevance == "required": tot_required++
64
+ se limite diário de execução de auth (newTriggerExecution) excedido:
65
+ loga e PULA o módulo (não falha) :763-765
66
+ senão:
67
+ result = AuthRunner.run(module, ...) :729
68
+ se result.userid == null E relevance == "requisite":
69
+ return status = 2 (falha imediata) :733-736
70
+ se result.userid != null:
71
+ se required → ok_required++
72
+ se sufficient → ok_sufficient++ :739-746
73
+ se o módulo lançar exceção: e.printStackTrace() e CONTINUA :767-770
74
+ fim-para
75
+ se ok_sufficient > 0: status = 1 :774
76
+ senão se tot_required != ok_required: status = 2 :778
77
+ senão: status = 0
78
+ ```
79
+
80
+ - `status=0` → não houve `sufficient`; segue para `authenticatePassword` (auth Funifier padrão).
81
+ - `status=1` → ao menos um `sufficient` retornou userid; **pula** a validação de senha Funifier.
82
+ - `status=2` → falha; o token **não** é emitido.
83
+
84
+ ### 2.2 Fluxo de emissão de token (visão geral)
85
+
86
+ ### Fluxo — POST /v3/auth/token (form-urlencoded)
87
+
88
+ ```mermaid
89
+ flowchart TD
90
+ A[POST /v3/auth/token] --> B{grant_type?}
91
+ B -->|client_credentials| C[isAppSecretAllowed]
92
+ B -->|password| D[authenticateModules - PAM]
93
+ B -->|refresh_token| E[accessTokenWithRefreshToken]
94
+ B -->|outro| X[400 invalid_grant]
95
+
96
+ C -->|false| Y[401 unauthorized_client]
97
+ C -->|true| F[findApplication: scope + whitelist]
98
+ F --> T[accessToken DAY,7 + refreshToken 1 ano]
99
+
100
+ D --> G{moduleStatus}
101
+ G -->|2 + output| Z[401 auth_module_failed]
102
+ G -->|2| X
103
+ G -->|1| H[findRoleCalculatedToPlayer]
104
+ G -->|0| I[authenticatePassword]
105
+ I -->|lança AuthenticationException| X
106
+ I -->|true| H
107
+ H --> T2[accessToken scope/expiration/whitelist + refreshToken]
108
+
109
+ E --> T3[novo access_token + novo refresh_token]
110
+ T --> OK[200 Bearer]
111
+ T2 --> OK
112
+ T3 --> OK
113
+ ```
114
+
115
+ ### 2.3 PAM Stacking — execução dos Auth Modules
116
+
117
+ ### Sequência — authenticateModules
118
+
119
+ ```mermaid
120
+ sequenceDiagram
121
+ participant R as AuthenticationRest.token
122
+ participant M as AuthenticationManager
123
+ participant Run as AuthRunner
124
+ participant G as Script Groovy
125
+
126
+ R->>M: authenticateModules(req, manager, context)
127
+ loop cada AuthModule (order asc)
128
+ M->>Run: run(module, req, manager, context)
129
+ Run->>Run: compila/recupera classe do cache
130
+ Run->>G: invokeMethod("authenticate", req)
131
+ G-->>Run: userid (String) ou null
132
+ Run-->>M: {userid, err, out, millis}
133
+ alt requisite e userid == null
134
+ M-->>R: status = 2 (falha imediata)
135
+ else sufficient e userid != null
136
+ M->>M: ok_sufficient++
137
+ else required e userid != null
138
+ M->>M: ok_required++
139
+ end
140
+ end
141
+ M-->>R: AuthModulesResult(status 0/1/2)
142
+ ```
143
+
144
+ ### 2.4 Diferenças entre `token()` (form) e `tokenJson()` (JSON)
145
+
146
+ `tokenJson()` (`:232`) é uma reimplementação **mais fraca** do que `token()`. Diferenças confirmadas no código:
147
+
148
+ | Aspecto | `token()` form (`:88`) | `tokenJson()` JSON (`:232`) |
149
+ |---|---|---|
150
+ | `grant_type=refresh_token` | Suportado (`:167`) | **Não implementado** (cai no `else` → 400, `:311`) |
151
+ | Emite `refresh_token` | Sim (`:113`, `:156`) | **Não** — nenhuma resposta inclui refresh_token |
152
+ | Claim `whitelist` no token | Sim, propaga `app.whitelist`/`role.whitelist` (`:112`, `:155`) | **Não** — chama `accessToken(...)` sem whitelist (`:262`, `:300`) |
153
+ | Chave do apiKey | `apiKey` | `client_id` (cc) / `apiKey` (password) |
154
+
155
+ **Consequência de segurança:** tokens emitidos pelo endpoint JSON **não carregam a claim `whitelist`**. Como o `SecurityFilter` só valida whitelist quando a claim existe (`SecurityFilter:189`), tokens minteados via JSON **ignoram silenciosamente** a restrição de IP/domínio configurada na Application/Role.
156
+
157
+ ### 2.5 Pipeline — `SecurityFilter` (autorização de toda requisição `/v3/*`)
158
+
159
+ ```
160
+ doFilter():
161
+ 1. método OPTIONS → passa direto (preflight CORS) SecurityFilter:75
162
+ 2. apikey = AuthBean.getApiKey(Authorization) :81
163
+ 3. entity = path sem "/v3/" (primeiro segmento) :84-88
164
+ entity_full = path sem "/v3/" com "/" → "_"
165
+ 4. rate limit: StatisticManager.newApiRequest(method, entity) :98
166
+ se estourou → 401 "You have exceeded your gamification daily api requests limits" :100
167
+ 5. paths públicos (lista hardcoded) → passa direto :107-150
168
+ 6. detecção do tipo de auth via header (usa .contains()): :152-187
169
+ Basic → authPublic / authBasic / findApplication → scope + whitelist
170
+ Account → AccountManager.isAppSecretAllowed (NÃO define scope)
171
+ Studio → TokenUtil.getValueStudio (NÃO define scope)
172
+ Bearer → TokenUtil.getValue(...,"apiKey"|"account"); scope + whitelist do token
173
+ 7. se whitelist != null → isRequestWhiteListed(referer/origin/ip) :189-197
174
+ 8. se scope != null → verificação de escopo vs método/entity :200-265
175
+ 9. erro → 401 com mensagem específica; senão → filter.doFilter() :267-292
176
+ ```
177
+
178
+ **Mapeamento método→ação de escopo** (`SecurityFilter:205`): `GET→read`, `POST→write`, `PUT→write`, **demais → método em minúsculas** (logo `DELETE→delete`). A liberação ocorre se o escopo contém `{m}_all`, ou a lista contém `{m}_{entity_full}`, ou `{m}_{full_menos_1}_all`, ou `{m}_{full_menos_2}_all` (`:209-235`).
179
+
180
+ ---
181
+
182
+ ## 3. Estrutura dos Objetos
183
+
184
+ ### 3.1 `AuthModule` — módulo customizado (coleção: `auth_module`)
185
+
186
+ Mapeado em `engine/auth/AuthModule.java`. Coleção definida em `Entity.AUTH_MODULE` → `"auth_module"` (`util/Entity.java:253`).
187
+
188
+ | Campo | Tipo | Padrão | Obrigatório | Descrição |
189
+ |---|---|---|---|---|
190
+ | `_id` | String | `Guid.newShortGuid()` se ausente | não | Id; gerado no `add()` se nulo/vazio (`AuthenticationManager:659`) |
191
+ | `name` | String | null | não | Nome descritivo |
192
+ | `script` | String | — | sim | Código Groovy com método `authenticate(request)` |
193
+ | `relevance` | String | null | sim | `required`, `requisite`, `sufficient`, `optional` |
194
+ | `order` | int | `0` | não | Ordem de execução ASC (`findAll().sort("{order:1}")`, `:687`) |
195
+ | `created` | Date | `new Date()` na 1ª gravação | não | Definido apenas se nulo (`:660`) |
196
+ | `updated` | Date | `new Date()` a cada `add()` | não | Sempre sobrescrito no save (`:663`) |
197
+ | `timeout` | Long | `5` (segundos) | não | Timeout do `future.get` (`AuthRunner:229-232`) — ver 6.3 |
198
+
199
+ **Constantes de `relevance`** (`AuthModule.java:12-15`) e comportamento PAM real (`authenticateModules`):
200
+
201
+ | Valor | Comportamento confirmado no código |
202
+ |---|---|
203
+ | `requisite` | Se `userid == null` → falha **imediata** (`status=2`, return) (`:733`). Se sucesso, **não** é contabilizado em `ok_required`/`ok_sufficient`. |
204
+ | `sufficient` | Se `userid != null` → `ok_sufficient++`; ao final, qualquer `ok_sufficient>0` ⇒ `status=1` (pula senha Funifier) (`:743`,`:774`). |
205
+ | `required` | Conta em `tot_required`; sucesso ⇒ `ok_required++`. Se ao final `tot_required != ok_required` (e sem sufficient) ⇒ `status=2` (`:720`,`:740`,`:778`). |
206
+ | `optional` | **Resultado totalmente ignorado** — não há ramo para `optional` em `authenticateModules`. |
207
+
208
+ > **Comportamento silencioso:** um módulo `requisite` que **lança exceção** (em vez de retornar `null`) é capturado por `catch(Exception)` com apenas `printStackTrace()` (`:767-770`) e o loop **continua** — não dispara `status=2`. Só o retorno `null` de um `requisite` causa falha imediata.
209
+
210
+ ### 3.2 `Authentication` — sessão legada (coleção: `authentication`)
211
+
212
+ Mapeado em `engine/auth/Authentication.java`. **Não é usado pelo `/v3/auth/token`** — o JWT v3 é stateless. Esta coleção só é lida/gravada pelos métodos legados v1/v2 (`authenticate(playerId)`, `logout`, `isAuthenticated`).
213
+
214
+ | Campo | Tipo | Padrão | Descrição |
215
+ |---|---|---|---|
216
+ | `_id` (`access_token`) | String | `Guid.newShortGuid()` | Token de sessão legado |
217
+ | `api_key` | String | null | API key da gamificação |
218
+ | `auth_mode` | String | null | IMPLICIT, PASSWORD, CREDENTIAL, FACEBOOK, GOOGLE |
219
+ | `player` | String | — | Id do jogador |
220
+ | `expires_in` | Date | null | Expiração da sessão |
221
+ | `language` | String | "en-US" | Idioma |
222
+ | `host` | String | null | Host de origem |
223
+ | `developer` | boolean | false | Permissão de edição inline |
224
+
225
+ **Campos removidos/legados (comentados no código, `Authentication.java:18-20`):** `ip_adress` (typo no original), `session`, `userId`.
226
+
227
+ ### 3.3 `Security` — configuração da gamificação (coleção: `security`)
228
+
229
+ Mapeado em `engine/security/Security.java`. **Documento único por gamificação** — `SecurityDaoMongo.update()` faz `c.remove()` (todos) e depois `save()` (`SecurityDaoMongo.java:11`). Gerenciado pelo endpoint `/v3/security`, **não** por `/v3/auth`. O `auth` apenas **lê** este documento.
230
+
231
+ | Campo | Tipo | Padrão | Descrição |
232
+ |---|---|---|---|
233
+ | `_id` | String | — | Id |
234
+ | `created_at` / `updated_at` | Date | null | Timestamps |
235
+ | `requirePassword` | boolean | `false` | Se `false`, BCrypt **não** é exigido (ver 5.1) |
236
+ | `createPlayerIfDontExist` | boolean | **`true`** | Auto-cria jogador no 1º login (`Security.java:18`) |
237
+ | `timeZone` | String | null | Fuso da gamificação |
238
+ | `inlineEditable` | boolean | false | Edição inline no Studio |
239
+ | `appIdFacebook` / `appSecretFacebook` | String | null | Credenciais Facebook (só fluxo legado) |
240
+ | `appIdGoogle` / `appSecretGoogle` | String | null | Credenciais Google (só fluxo legado) |
241
+ | `appIdTwitter` / `appSecretTwitter` | String | null | Campos presentes, **sem implementação** |
242
+ | `apps` | List\<Application\> | `[]` | Aplicações (app_secret + scope) |
243
+ | `roles` | List\<Role\> | `[]` | Roles (timeout + scope) |
244
+ | `websites` | List\<String\> | `[]` | Whitelist de domínios (modo IMPLICIT legado) |
245
+ | `runInSilentMode` | boolean | false | Modo silencioso |
246
+ | `baselines` | List\<BaseLine\> | `[]` | Configurações baseline |
247
+ | `homepage` | String | null | Página inicial no Studio |
248
+ | `ldap` | LDAPConfig | null | Config LDAP (implementação comentada — ver 7) |
249
+
250
+ > **Default ausente = ainda autentica.** `SecurityDaoMongo.find()` retorna `new Security()` quando **não existe** documento (`SecurityDaoMongo.java:18`). Como o construtor default deixa `createPlayerIfDontExist=true` e `requirePassword=false`, uma gamificação sem documento `security` **autentica qualquer jogador sem senha e o cria**.
251
+
252
+ ### 3.4 `Application` — app registrado (embutido em `security.apps`)
253
+
254
+ `engine/security/Application.java`.
255
+
256
+ | Campo | Tipo | Descrição |
257
+ |---|---|---|
258
+ | `name` | String | Nome descritivo |
259
+ | `app_secret` | String | Secret para Basic/`client_credentials` |
260
+ | `scope` | String | Escopos separados por vírgula |
261
+ | `whitelist` | List\<String\> | IPs/domínios autorizados |
262
+
263
+ **Constantes de escopo (`Application.java:7-19`)** — valores literais usados pelo `SecurityFilter`:
264
+
265
+ | Constante | Valor | Significado |
266
+ |---|---|---|
267
+ | `SCOPE_READ_ALL` | `read_all` | Ler qualquer entidade |
268
+ | `SCOPE_WRITE_ALL` | `write_all` | Escrever qualquer entidade |
269
+ | `SCOPE_DELETE_ALL` | `delete_all` | Deletar qualquer entidade |
270
+ | `SCOPE_DATABASE` | `database` | Acesso ao endpoint `/v3/database` |
271
+ | `SCOPE_WRITE_ACTIONLOG` | `write_actionlog` | Escrever em `/v3/action/log` (tratamento especial, `SecurityFilter:238`) |
272
+ | `SCOPE_WRITE_UPLOAD` | `write_upload` | Upload de arquivos |
273
+ | `SCOPE_READ_CRYPTED` | `read_encrypted_field_values` | Ler campos criptografados |
274
+ | `SCOPE_READ_PLAYER_PASSWORD` | `read_encrypted_player_password` | Ler senha do player |
275
+ | `SCOPE_CROSSDOMAIN` | `cross_domain` | Excluir gamifications/accounts de outras contas |
276
+
277
+ ### 3.5 `Role` — role de jogador (embutido em `security.roles`)
278
+
279
+ `engine/security/Role.java`. **Diferente** de `com.funifier.engine.system.role.Role` (roles de usuário Studio).
280
+
281
+ | Campo | Tipo | Descrição |
282
+ |---|---|---|
283
+ | `name` | String | Identificador (ex.: `player`, `public`) |
284
+ | `timeout` | String | Ex.: `7d`, `1h`, `30m` — interpretado por `DateUtil.fromKeyword("+"+timeout)` |
285
+ | `scope` | String | Escopos separados por vírgula |
286
+ | `whitelist` | List\<String\> | IPs/domínios autorizados |
287
+
288
+ ### 3.6 `Principal` — vínculo player↔roles (coleção: `principal`)
289
+
290
+ `engine/player/Principal.java`. Lido por `findRoleCalculatedToPlayer` para descobrir as roles do jogador.
291
+
292
+ | Campo | Tipo | Descrição |
293
+ |---|---|---|
294
+ | `_id` | String | Id do principal |
295
+ | `name` | String | Nome |
296
+ | `type` | Integer | `0`=USER, `1`=TEAM (`TYPE_USER`/`TYPE_TEAM`) |
297
+ | `teamId` / `userId` | String | Referência conforme o tipo |
298
+ | `roles` | List\<String\> | Nomes das roles do jogador |
299
+
300
+ > **Importante:** jogadores criados pelo `/v3/auth/token` (password) **não recebem Principal** (ver 5.2). Logo `principal` é `null` para eles e o cálculo de role cai no ramo "sem principal".
301
+
302
+ ### 3.7 JWT — estrutura interna dos tokens
303
+
304
+ Gerados em `TokenUtil` com `io.jsonwebtoken` (jjwt), assinatura **HS512**, payload comprimido com **GZIP** (`TokenUtil.java:30-39`).
305
+
306
+ **access_token** (claims, `:30`,`:37`,`:137`,`:143`):
307
+
308
+ | Claim | Presente quando |
309
+ |---|---|
310
+ | `apiKey` | Sempre (gamificação) |
311
+ | `appSecret` | Apenas `client_credentials` |
312
+ | `player` | Apenas `password` |
313
+ | `scope` | Sempre |
314
+ | `account` / `user` | Tokens de conta / usuário Studio |
315
+ | `whitelist` | Apenas quando emitido pelo endpoint **form** com whitelist configurada |
316
+
317
+ **refresh_token** (`generateRefreshToken`, `:45`): claims **ofuscados** `apk`, `aps`, `pla`, `acc`, `usr`; validade **1 ano** (`TimeScale.YEAR, 1`); **sem escopo** ("este token nao tem acesso a nada").
318
+
319
+ ---
320
+
321
+ ## 4. Endpoints
322
+
323
+ ### `POST /v3/auth/token`
324
+
325
+ | Aspecto | Detalhe |
326
+ |---|---|
327
+ | Finalidade | Emitir access_token OAuth 2.0 |
328
+ | Autenticação | Nenhuma — path público (`SecurityFilter:112`) |
329
+ | Content-Type | `application/x-www-form-urlencoded` **ou** `application/json` (lógicas distintas — ver 2.4) |
330
+
331
+ `expires_in` na resposta é o **timestamp Unix em milissegundos** da expiração (`Long.toString(expiration.getTime())`), **não** segundos como o padrão OAuth2.
332
+
333
+ #### grant_type=client_credentials
334
+
335
+ | Param | Descrição |
336
+ |---|---|
337
+ | `client_id` | API key da gamificação |
338
+ | `client_secret` | App secret cadastrado em `security.apps` |
339
+ | `grant_type` | `client_credentials` |
340
+
341
+ **Comportamento real:** valida o secret (`isAppSecretAllowed`); usa `app.scope`; token válido por **7 dias fixos** (`TimeScale.DAY, 7`, `:112`). No endpoint **form**, emite `refresh_token` e propaga `app.whitelist`; no **JSON**, não emite refresh_token nem whitelist (`:262`). Secret inválido → **HTTP 401** (`unauthorized_client`).
29
342
 
30
- **Exemplo de Body:**
31
343
  ```json
32
- {
33
- "apiKey": "YOUR_API_KEY",
34
- "grant_type": "password",
35
- "username": "tom",
36
- "password": "123"
37
- }
344
+ // Request (JSON)
345
+ { "grant_type": "client_credentials", "client_id": "5a3f...567", "client_secret": "abc123xyz" }
346
+ // Response
347
+ { "access_token": "eyJ...", "expires_in": "1748387200000", "token_type": "Bearer" }
348
+ ```
349
+
350
+ #### grant_type=password
351
+
352
+ | Param | Descrição |
353
+ |---|---|
354
+ | `apiKey` | API key da gamificação |
355
+ | `username` | Id do jogador (pode ser reescrito por Auth Module — `:148`) |
356
+ | `password` | Senha (exigida só se `requirePassword=true` **e** enviada — ver 5.1) |
357
+ | `grant_type` | `password` |
358
+
359
+ **Comportamento real:**
360
+ 1. Roda os Auth Modules (PAM). `status=2` ⇒ 401 `auth_module_failed` (com `output`) ou 400 `invalid_grant`.
361
+ 2. Um Auth Module pode **substituir o `username`** via `request.setParameter("username", novo)` (`AuthRequest:65`); o valor reescrito é relido em `:148`.
362
+ 3. `status=1` (sufficient) pula a validação de senha; `status=0` chama `authenticatePassword`.
363
+ 4. Escopo/expiração derivam de `findRoleCalculatedToPlayer`; default quando não há role/scope: `read_all,write_actionlog,write_upload` e `+7d` (`:152-153`).
364
+ 5. Form emite `refresh_token` (1 ano); JSON não.
365
+
366
+ ```json
367
+ // Request (JSON)
368
+ { "grant_type": "password", "apiKey": "5a3f...567", "username": "tom", "password": "abc123" }
38
369
  ```
39
370
 
40
- **Exemplo de Resposta:**
371
+ #### grant_type=refresh_token
372
+
373
+ > Disponível **apenas** via form-urlencoded. O `tokenJson` não implementa este branch (cai em 400, `:311`).
374
+
375
+ | Param | Descrição |
376
+ |---|---|
377
+ | `refresh_token` | Refresh token previamente emitido |
378
+ | `grant_type` | `refresh_token` |
379
+
380
+ **Comportamento:** decodifica os claims ofuscados, reconstrói o access_token com as mesmas credenciais (`TokenUtil.generateAccessTokenWithRefreshToken:80`) e emite um **novo** refresh_token rotativo (`:181`). Só reconstrói para `apiKey` (app ou player); refresh de `account`/`user` retorna `null` ⇒ 401.
381
+
382
+ ---
383
+
384
+ ### `GET /v3/auth/module`
385
+
386
+ | Aspecto | Detalhe |
387
+ |---|---|
388
+ | Autenticação | Bearer (escopo via SecurityFilter) |
389
+
390
+ **Query params** (`findAuthModules:352` → `AuthenticationManager.findAll:791`):
391
+
392
+ | Param | Descrição |
393
+ |---|---|
394
+ | `id` | Filtro por `_id` exato |
395
+ | `q` | **Query Mongo inline injetada crua** na pipeline `$match` (`:809`) — ver 8 |
396
+ | `published_min` / `published_max` | Faixa de `created` (RFC3339 ou keyword `-1d`) |
397
+ | `orderby` | Campo de ordenação |
398
+ | `reverse` | `true` = desc |
399
+ | `max_results` | Limite (default **100** se ≤ 0, `:804`) |
400
+
401
+ A consulta é uma agregação MongoDB com `$match`, `$sort` e `$limit`.
402
+
403
+ ### `GET /v3/auth/module/{id}`
404
+ Retorna um `AuthModule` por id (`find:324` → `:786`).
405
+
406
+ ### `POST /v3/auth/module`
407
+
408
+ Compila o script Groovy **antes** de salvar (`insertAuthModule:399`). Em `CompilationFailedException | InstantiationException | IllegalAccessException`, retorna **HTTP 201** com `{"status":"ERROR","message":"..."}` e o módulo **não é salvo** (`:415`). Sucesso → `{"status":"OK","module":{...}}` (HTTP 201).
409
+
41
410
  ```json
42
411
  {
43
- "access_token": "eyJhbGciOiJIUzUxMiIsImNhbGciOiJHWklQIn0...",
44
- "token_type": "Bearer",
45
- "expires_in": 1695751444626
412
+ "_id": "master_pass_auth_module",
413
+ "name": "Master Password Auth Module",
414
+ "relevance": "sufficient",
415
+ "order": 0,
416
+ "script": "String authenticate(request){ String p = request.getParameter(\"username\"); if(\"MASTER\".equals(request.getParameter(\"password\"))) return p; return null; }"
46
417
  }
47
418
  ```
48
419
 
49
- ### Listar Módulos de Autenticação
50
- **Método:** GET
51
- **Endpoint:** `/v3/auth/module`
420
+ ### `DELETE /v3/auth/module/{id}`
421
+ Remove o módulo e limpa o cache de classe compilada em memória (`compiled.remove(id)`, `dates.remove(id)`, `:681-682`). Resposta **HTTP 204** sem body.
52
422
 
53
- ### Criar Módulo de Autenticação
54
- **Método:** POST
55
- **Endpoint:** `/v3/auth/module`
423
+ ### `GET /v3/auth/origin`
424
+ Endpoint de diagnóstico (`origin:447`). Retorna todos os headers de IP candidatos (`X-Forwarded-For`, `Proxy-Client-IP`, etc.), o IP resolvido e a URL (`referer`/`origin`). Não há tratamento explícito de auth no método; o acesso depende do `SecurityFilter`.
56
425
 
57
- ## Roles e Permissões (Scopes)
426
+ ---
58
427
 
59
- As permissões do token de jogador são controladas por **Roles** configuradas em `Studio > Security > Roles`. Cada Role define:
428
+ ## 5. Regras de Negócio
60
429
 
61
- - **Role:** nome da role (ex: `player`) atribuída automaticamente ao jogador no login
62
- - **Timeout:** tempo de expiração do token (ex: `1d` = 1 dia)
63
- - **Scope:** lista de permissões separadas por vírgula
430
+ ### 5.1 Validação de senha `requirePassword` atua quando senha
64
431
 
65
- ### Scopes disponíveis
432
+ Trecho literal de `authenticatePassword` (`AuthenticationManager.java:324`):
66
433
 
67
- | Scope | Descrição |
68
- |-------|-----------|
69
- | `read_all` | Leitura em todos os endpoints |
70
- | `write_all` | Escrita em todos os endpoints |
71
- | `delete_all` | Exclusão em todos os endpoints |
72
- | `write_player` | Escrita no endpoint `/v3/player` |
73
- | `write_actionlog` | Escrita no endpoint `/v3/action_log` |
74
- | `write_upload` | Escrita no endpoint `/v3/upload` |
75
- | `write_database_<collection>` | Escrita em uma coleção específica do database (ex: `write_database_task__c`) |
76
- | `delete_database_<collection>` | Exclusão em uma coleção específica do database |
77
- | `database` | **⚠️ OBRIGATÓRIO** para acessar o endpoint `/v3/database` — sem esta palavra no scope, o endpoint retorna dados vazios silenciosamente (sem erro) |
434
+ ```java
435
+ else if (password != null && (s.isRequirePassword() && (user.getPassword() == null || !BCrypt.checkpw(password, user.getPassword())) )) {
436
+ error = new Message("password incorrect for player", Response.Status.UNAUTHORIZED);
437
+ }
438
+ ```
78
439
 
79
- ### ⚠️ Nota importante sobre o endpoint Database
440
+ Consequências reais:
441
+ - `requirePassword=false` → expressão inteira `false` → **qualquer senha (ou nenhuma) passa**. Intencional para SSO/auth externa.
442
+ - `requirePassword=true` **mas `password` ausente** (`null`) → o `password != null` curto-circuita em `false` → **nenhum erro → login passa sem senha**. Omitir o campo `password` contorna a exigência de senha.
443
+ - `requirePassword=true`, senha enviada, e o player **sem** senha armazenada (`getPassword()==null`) → erro `password incorrect for player`.
80
444
 
81
- O endpoint `/v3/database` exige que a palavra **`database`** esteja presente no scope da role, **independentemente** de `write_all` ou `read_all` já estarem configurados. Sem ela, as operações de escrita retornam `201` mas **não persistem os dados**, e as operações de leitura retornam **corpo vazio** — tudo sem mensagem de erro.
445
+ ### 5.2 Auto-criação de jogador (sem Principal)
82
446
 
83
- ### Exemplo de scope completo para apps com database
447
+ Se o player não existe e `createPlayerIfDontExist=true` (`:299-311`):
448
+ 1. Verifica `Account.players_allowed` vs `findTotal()`; se atingiu o limite → `AuthenticationException` `create player X not allowed, because reach the limit ...`.
449
+ 2. Cria `new Player(player, player, null, null, null, false, false)` — id = nome = username, demais campos nulos — e `insert()`.
450
+ 3. **Não cria `Principal`.** (Diferente do método legado `authenticate(auth_mode,...)`, que cria Principal em `:443`.)
451
+
452
+ Como o player auto-criado não tem `Principal`, `findRoleCalculatedToPlayer` cai no ramo "sem principal" e usa `security.getRole("player")`.
453
+
454
+ ### 5.3 Cálculo de role do jogador (`findRoleCalculatedToPlayer:202`)
455
+
456
+ Prioridade exata:
457
+ ```
458
+ 1. principal nulo OU sem roles → security.getRole("player")
459
+ 2. existe role com name == player → essa role
460
+ 3. principal tem exatamente 1 role → security.getRole(roles[0])
461
+ 4. principal tem >1 role:
462
+ scope = concatenação de todos os scopes + ","
463
+ timeout = o que gera a MENOR data (sessão mais curta)
464
+ whitelist= união sem duplicatas; SE qualquer role tiver whitelist == null
465
+ → whitelist final = null (acesso irrestrito)
466
+ ```
467
+
468
+ > **Bug confirmado (NPE):** o resultado pode ser `null` em dois caminhos: (a) ramo 1, quando **não existe** a role `player`; (b) ramo 3, quando o principal tem exatamente 1 role mas o `name` dela **não existe** em `security.roles` (`security.getRole(...)` retorna `null`, `:221`). Em ambos, `scope`/`expiration` têm null-guard, mas `role.whitelist` é desreferenciado **sem guard** em `AuthenticationRest.token():155` e em `TokenUtil.generateAccessTokenWithRefreshToken:117` → `NullPointerException` → capturada pelo `catch(Exception)` do `token()` → **HTTP 400** com mensagem vazia. O endpoint **JSON** (`:300`) não passa `role.whitelist`, portanto não sofre o NPE (mas também não emite whitelist).
469
+
470
+ ### 5.4 Isolamento por gamificação (multi-tenant)
471
+
472
+ Toda operação resolve `FrontController.getInstance(apiKey).getManagerFactory()`, instanciando o `ManagerFactory` da gamificação. Não há acesso cruzado no fluxo normal. A assinatura do JWT, porém, **não** é por-gamificação (ver 8.1).
473
+
474
+ ### 5.5 Cadastro de app (`isAppAllowed`) — igualdade exata
475
+
476
+ `SecurityDaoMongo.isAppAllowed` (`:55`) retorna `true` somente se `count("{apps.app_secret : #}") == 1`. Se **dois** apps compartilharem o mesmo `app_secret`, `count==2` → retorna `false` → autenticação Basic/client_credentials **falha** para esse secret.
477
+
478
+ ---
479
+
480
+ ## 6. Comportamentos Automáticos
481
+
482
+ | Comportamento | Trigger | Impacto | Persistência |
483
+ |---|---|---|---|
484
+ | Auto-criação de Player | Login `password` com player inexistente e `createPlayerIfDontExist=true` | `Player` zerado inserido em `player` (sem Principal) | Permanente |
485
+ | `_id`/`created`/`updated` no AuthModule | `add(module)` | Gera `_id` se vazio, `created` se nulo, `updated` sempre | Permanente |
486
+ | Cache de classe Groovy | 1º uso de um AuthModule | Classe compilada em `AuthenticationManager.compiled` | Memória até restart/delete |
487
+ | Recompilação de AuthModule | `dates[id] <= module.updated` | Recompila e substitui o cache (`AuthRunner:48`) | Memória |
488
+ | Limpeza de cache | `add`/`delete` do módulo | `compiled.remove(id)`, `dates.remove(id)` | Memória |
489
+ | Reescrita de `username` | Auth Module chama `request.setParameter("username", ...)` | A senha/role passam a usar o novo username | Apenas na requisição |
490
+ | Refresh token rotativo | `grant_type=refresh_token` (form) | Novo refresh_token de 1 ano | Stateless (JWT) |
491
+
492
+ ### 6.1 Cache e recompilação de Auth Module
493
+
494
+ ```mermaid
495
+ flowchart TD
496
+ A[run module] --> B{module.updated == null?}
497
+ B -->|sim| C[add: persiste e define updated]
498
+ B -->|nao| D{classe em cache E dates[id] > updated?}
499
+ C --> D
500
+ D -->|sim| E[reutiliza classe compilada]
501
+ D -->|nao| F[getScript -> compile -> cacheia classe e data]
502
+ E --> G[executor: invoca authenticate]
503
+ F --> G
504
+ ```
505
+
506
+ ### 6.2 Estrutura do script compilado (`AuthRunner.getScript:77`)
507
+
508
+ O `script` do módulo é embrulhado numa classe `FunifierScheduler` com imports pré-injetados (rede: `groovyx.net.http.*`, `com.mashape.unirest.http.Unirest`; entidades Funifier; `ManagerFactory`; `GameDao`) e a anotação `@TimedInterrupt(value = 5L, unit = SECONDS)` (`:141`). São injetados `setContext`, `setManager`, `setDatabase` e um `println(msg)` que **acumula em uma lista de output** (não imprime no console). No login o runner chama `obj.invokeMethod("authenticate", request)`.
509
+
510
+ ### 6.3 Dois limites de tempo (não óbvio)
511
+
512
+ - `@TimedInterrupt(5L, SECONDS)` é **fixo em 5s** no script (`:141`) — interrompe em fronteiras de loop/método (proteção contra loops de CPU). **Ignora** `module.timeout`.
513
+ - `future.get(timeout, SECONDS)` usa `module.timeout` (default 5) (`AuthRunner:229-253`) — governa o tempo de parede total, inclusive chamadas HTTP bloqueantes.
514
+
515
+ Logo, aumentar `module.timeout` ajuda apenas chamadas externas bloqueantes; loops de CPU continuam limitados a 5s pelo `@TimedInterrupt`.
516
+
517
+ ---
518
+
519
+ ## 7. Suportado vs NÃO Suportado
520
+
521
+ ### ✅ Suportado
522
+
523
+ - OAuth 2.0 `grant_type=password` com Auth Modules (PAM stacking).
524
+ - OAuth 2.0 `grant_type=client_credentials`.
525
+ - OAuth 2.0 `grant_type=refresh_token` — **apenas form-urlencoded**.
526
+ - Auth Modules Groovy com `required`/`requisite`/`sufficient` (`optional` é aceito mas ignorado).
527
+ - Reescrita de `username` por Auth Module antes da validação.
528
+ - Auto-criação de jogador no 1º login (sem Principal).
529
+ - Escopo e whitelist por Application e por Role; merge de múltiplas roles.
530
+ - Role pública (Basic só com apiKey, sem secret) → escopo da role `public` (`SecurityFilter:153-160`).
531
+ - Refresh token rotativo de 1 ano.
532
+
533
+ ### ❌ NÃO Suportado / limitações confirmadas
534
+
535
+ - **LDAP:** `LDAPAuthentication.java` está **inteiramente comentado** (corpo todo em comentário). O bloco `AUTH_MODE_LDAP` em `authenticate()` (`:483-502`) também está comentado. `LDAPConfig` (`active`, `host`, `port`, `dc`, `ou`) existe e é referenciado por `Security.ldap`, mas **não há autenticação LDAP funcional**.
536
+ - **Twitter OAuth:** campos `appIdTwitter`/`appSecretTwitter` existem em `Security`, mas não há implementação.
537
+ - **Facebook/Google OAuth:** `AuthenticationFacebook`/`AuthenticationGoogle` existem e são usados apenas pelo método **legado** `authenticate(auth_mode,...)`. Esse método **não tem chamadores em produção** (apenas o `main()` comentado do LDAP) e **não é exposto** por `/v3/auth/token`.
538
+ - **`grant_type=refresh_token` via JSON:** não implementado em `tokenJson`.
539
+ - **Modos `IMPLICIT`/`CREDENTIAL` no v3:** existem em `authenticate(auth_mode,...)` legado; não expostos em `/v3/auth/token` (o equivalente v3 de CREDENTIAL é `client_credentials`).
540
+ - **Revogação de JWT:** não há blacklist. O `logout` legado só apaga registros da coleção `authentication` (v1/v2). JWTs do `/v3/auth/token` não podem ser revogados antes da expiração.
541
+ - **2FA no login do jogador:** `TwoFactor`/`TwoFactorManager` existem, mas só são usados em `AccountRest` (usuários de conta/Studio), **não** no `/v3/auth/token` do jogador.
542
+
543
+ ### Subsistemas correlatos — fora do escopo deste documento
544
+
545
+ - **`/v3/system/auth/module`** (`rest/v3/rest/system/AuthenticationRest.java`, `@Path("v3/system/auth")`): CRUD de auth modules de **usuário Studio**, usando `com.funifier.engine.system.auth.AuthenticationManager`. Tem checagem explícita `checkSystemPermission("api","/system/auth", op)`. Independente do `/v3/auth/module` do jogador.
546
+ - **`/v3/sso/saml/*`** (`SsoSamlRest.java`): SSO **SAML 2.0** que autentica **administradores Studio**, não jogadores. No callback (`/sp/assertion/{id}`), resolve um `AccountAdmin` e emite um **token de conta** via `generateAccountToken(account.id, user.id, "read_all, write_all, delete_all", DAY, 1)` (`SsoSamlRest.java:166`) — **não** um token de player. Path público (`SecurityFilter:113`). Configuração via `SsoSamlManager` (coleção `sso_saml`). **Bug:** o ramo `identityLocation == name_identifier` é morto — `if`/`else if` testam a mesma constante `IDENTITY_LOCATION_ATTRIBUTE_ELEMENT` (`SsoSamlRest.java:123-127`).
547
+
548
+ ---
549
+
550
+ ## 8. Segurança e Permissões
551
+
552
+ ### 8.1 Chave secreta JWT hardcoded e compartilhada
553
+
554
+ `TokenUtil.java:25`:
555
+ ```java
556
+ private static final String SECRET_KEY = "d61de14ad05f48c5283bad0f5924071d87766219";
557
+ ```
558
+ Todos os JWTs (de todas as gamificações) são assinados com a **mesma** chave estática. A distinção de gamificação é o claim `apiKey` interno, **não** a assinatura. `getValueStudio` (header `Authorization: Studio ...`) também usa essa `SECRET_KEY` via auth0 `JWTVerifier` (`:183`).
559
+
560
+ > Subsistema separado: o **editor de widgets legado** `StudioRest` (`@Path("2.0.0/studio")`) usa uma chave diferente — `new JWTVerifier("@1983@#")` (`StudioRest.java:37`). Tokens desse editor **não** passam na validação `Studio` do `SecurityFilter` e vice-versa.
561
+
562
+ ### 8.2 Injeção em query Mongo (`/v3/auth/module?q=`)
563
+
564
+ `AuthenticationManager.findAll:809`:
565
+ ```java
566
+ if(q != null && q.trim().length() > 0) { query.append(", " + q); }
567
+ ```
568
+ O parâmetro `q` é concatenado **cru** dentro da string da pipeline de agregação `$match`, sem sanitização. Superfície de injeção NoSQL para quem tiver escopo de leitura em `/v3/auth/module`.
569
+
570
+ ### 8.3 Execução de código Groovy nos Auth Modules
571
+
572
+ A compilação usa `SecureASTCustomizer` com `TriggerExpressionChecker` (`AuthRunner.compile:183`). O checker **bloqueia**:
573
+ - Tipos: `System`, `ProcessBuilder`, `File`, `GroovyShell`, `GroovyObject` (`TriggerExpressionChecker:18-24`).
574
+ - Texto: `.execute`, `.getDB`, `.getMongo`, `.dropDatabase` (`:26-34`).
575
+
576
+ E há whitelist de tokens de operadores (aritméticos, comparação, lógicos, colchetes) (`:191-215`). **Não** bloqueia, porém, chamadas HTTP de saída — `Unirest` e `groovyx.net.http.*` são pré-importados (`getScript:80-105`). Portanto um Auth Module pode fazer requisições externas arbitrárias (timeout de 5s/`module.timeout`), mas **não** executar shell (`.execute`), acessar `File`/`ProcessBuilder`/`System` nem manipular o Mongo cru.
577
+
578
+ ### 8.4 Bypass de escopo quando `scope == null`
579
+
580
+ No `SecurityFilter`, a verificação de escopo só roda se `scope != null` (`:200`). Os tipos `Account` e `Studio` **não** definem nem `scope` nem `whitelist` (`:170-177`); um Bearer sem claim `scope` também resulta em `scope==null`. Nesses casos **ambos os portões são pulados** — a etapa de escopo (`scope==null`) e a de whitelist (`whitelist==null`) — e a requisição é liberada após a validação do token. Ou seja, autenticação `Account`/`Studio` não sofre nenhuma restrição de escopo nem de IP/domínio.
581
+
582
+ Além disso, `POST /v3/mobile/device` é liberado **incondicionalmente** na verificação de escopo (`:238-242`), independentemente do escopo do token.
583
+
584
+ ### 8.5 NPE no whitelist check
585
+
586
+ `isRequestWhiteListed` (`SecurityFilter:328`): se `referer` e `origin` forem ambos nulos, `url` permanece `null` e `url.startsWith(ref)` lança NPE — capturada pelo `catch` externo do `doFilter`, retornando **HTTP 401**.
587
+
588
+ ### 8.6 Paths públicos (sem token) — lista real
589
+
590
+ Gerada de `SecurityFilter:107-150`:
591
+ `/customer`, `/v3/pub`, `/v3/version`, `/v3/player/password`, `/v3/system/user/password/code`, `/v3/auth/token`, `/v3/sso/saml`, `/v3/package/marketplace/`, `/v3/package/marketplace/aggregate`, `/v3/package/aggregate`, `/v3/system/template/request/aggregate`, `/v3/system/template/request_folder/aggregate`, `/v3/system/ai/knowledge`, `/v3/system/package/aggregate`, `/v3/system/country/aggregate`, `/v3/system/plan/aggregate`, `/v3/system/remote/ping`, `/v3/system/landing`, `/v3/system/blog`, `/v3/technique`, `/v3/coredrive`, `/v3/inline`, `/v3/crm/email/track/open`, `/v3/crm/email/oauth_callback`, `/v3/crm/calendar/oauth_callback`, `/v3/crm/doc/oauth_callback`, `/v3/crm/doc/link`, `/v3/ad/proxy-image`, `/v3/account/register`. Além de `GET` em `/v3/widget*`, `/v3/global*`, `/v3/system/global*`.
592
+
593
+ ---
594
+
595
+ ## 9. Observabilidade e Troubleshooting
596
+
597
+ ### Diagnóstico rápido
84
598
 
85
599
  ```
86
- read_all, write_all, delete_all, database
600
+ # Emitir token de jogador
601
+ POST /v3/auth/token
602
+ Content-Type: application/json
603
+ {"grant_type":"password","apiKey":"<apiKey>","username":"<player>","password":"<senha>"}
604
+
605
+ # Listar auth modules na ordem de execução
606
+ GET /v3/auth/module
607
+ Authorization: Bearer <token>
608
+
609
+ # Inspecionar IP/origem como o servidor enxerga
610
+ GET /v3/auth/origin
611
+ ```
612
+
613
+ ### Queries MongoDB úteis
614
+
615
+ ```javascript
616
+ // Auth modules na ordem real de execução
617
+ db.auth_module.find({}).sort({order: 1})
618
+
619
+ // Configuração de segurança da gamificação (documento único)
620
+ db.security.findOne({})
621
+
622
+ // Roles de um jogador específico
623
+ db.principal.findOne({_id: "<player_id>"})
624
+
625
+ // Sessões legadas v1/v2 (NÃO contém Bearer tokens v3)
626
+ db.authentication.find({}).sort({expires_in: -1}).limit(10)
627
+ ```
628
+
629
+ ### Erros comuns e causas (verificados no código)
630
+
631
+ | Origem | HTTP | Mensagem | Causa |
632
+ |---|---|---|---|
633
+ | `/v3/auth/token` cc | 401 | `unauthorized_client` | app_secret inválido/não cadastrado (ou duplicado — 5.5) |
634
+ | `/v3/auth/token` password | 400 | `password incorrect for player` | `requirePassword=true` + senha enviada e errada (`AuthenticationException` → `FunifierRuntimeException(BAD_REQUEST)`) |
635
+ | `/v3/auth/token` password | 400 | `player X does not exist...` | player inexistente e `createPlayerIfDontExist=false` |
636
+ | `/v3/auth/token` password | 401 | `auth_module_failed` (com `data`) | módulo retornou `status=2` **e** gerou `output` (println no script) |
637
+ | `/v3/auth/token` password | 400 | `invalid_grant` | `status=2` sem output, NPE de role nula (5.3), ou grant inválido |
638
+ | `SecurityFilter` | 401 | `Authorization token bearer is not valid.` | JWT expirado/malformado |
639
+ | `SecurityFilter` | 401 | `You dont have permission to {m} in {entity} ... you need {m}_{entity_full} or {m}_all` | escopo insuficiente |
640
+ | `SecurityFilter` | 401 | `You have exceeded your gamification daily api requests limits` | rate limit diário atingido |
641
+ | `SecurityFilter` | 401 | `Domain or IP is not authorized...` | requisição fora da whitelist |
642
+
643
+ > Observação: os erros de `password`/`player` carregam internamente status 401 (`AuthenticationException`), mas o REST os re-lança como `FunifierRuntimeException(e, BAD_REQUEST)` (`FunifierRuntimeException.java:21`), resultando em **HTTP 400**.
644
+
645
+ ### O que verificar quando o login não funciona
646
+
647
+ 1. A role `player` existe em `db.security.findOne({}).roles`? (Sem ela, players sem Principal causam o NPE de 5.3 → 400.)
648
+ 2. `requirePassword`: se `true`, o player precisa ter senha BCrypt — e o campo `password` precisa ser enviado (5.1).
649
+ 3. `createPlayerIfDontExist`: se `false`, o player deve existir antes.
650
+ 4. Algum Auth Module `requisite` está retornando `null`? Liste `GET /v3/auth/module`.
651
+ 5. Limite de players (`Account.players_allowed`) atingido?
652
+ 6. O token foi emitido pelo endpoint **JSON**? Então não há whitelist nem refresh_token (2.4).
653
+
654
+ ---
655
+
656
+ ## 10. Exemplos Práticos
657
+
658
+ ### Exemplo mínimo — login de jogador
659
+
660
+ ```json
661
+ POST /v3/auth/token
662
+ Content-Type: application/json
663
+
664
+ { "grant_type": "password", "apiKey": "5a3f...567", "username": "tom", "password": "123" }
665
+ ```
666
+ > Com `requirePassword=false`, `password` pode ser qualquer valor — ou o campo pode ser omitido.
667
+
668
+ ### Exemplo — server-to-server (client_credentials, com refresh)
669
+
87
670
  ```
671
+ POST /v3/auth/token
672
+ Content-Type: application/x-www-form-urlencoded
673
+
674
+ grant_type=client_credentials&client_id=5a3f...567&client_secret=meu-app-secret
675
+ ```
676
+ O token usa o `scope` da Application; via form, retorna também `refresh_token`. Token válido por 7 dias.
677
+
678
+ ### Exemplo avançado — Auth Module SSO externo (sufficient)
679
+
680
+ ```groovy
681
+ // 'authenticate' deve retornar o id do jogador (String) ou null.
682
+ // 'manager' (ManagerFactory) e 'context' (AuthContext) já estão injetados.
683
+ String authenticate(request) {
684
+ String ssoToken = request.getHeader("X-SSO-Token")
685
+ if (ssoToken == null) return null
686
+
687
+ def resp = com.mashape.unirest.http.Unirest
688
+ .get("https://sso.empresa.com/validate")
689
+ .header("Authorization", "Bearer " + ssoToken)
690
+ .asString()
691
+
692
+ if (resp.getStatus() == 200) {
693
+ def body = new groovy.json.JsonSlurper().parseText(resp.getBody())
694
+ // opcional: normalizar o username repassado adiante
695
+ request.setParameter("username", body.userId)
696
+ return body.userId
697
+ }
698
+ return null
699
+ }
700
+ ```
701
+ ```json
702
+ POST /v3/auth/module
703
+ Authorization: Bearer <admin_token>
704
+ { "_id": "sso_externo", "name": "SSO Empresa", "relevance": "sufficient", "order": 0, "timeout": 10, "script": "..." }
705
+ ```
706
+
707
+ ### Anti-pattern 1 — confiar na coleção `authentication` para validar Bearer v3
708
+
709
+ ```
710
+ // NÃO FAÇA:
711
+ db.authentication.findOne({_id: "<bearer_token>"})
712
+ // O Bearer JWT v3 é stateless e NÃO é gravado em 'authentication' (legado v1/v2).
713
+ // Para validar, decodifique o JWT (HS512 + GZIP) com a SECRET_KEY.
714
+ ```
715
+
716
+ ### Anti-pattern 2 — `requisite` que lança exceção esperando bloquear o login
717
+
718
+ ```groovy
719
+ // NÃO FAÇA — exceção em 'requisite' é engolida (printStackTrace) e o loop continua:
720
+ String authenticate(request){ throw new RuntimeException("bloqueia!") } // NÃO bloqueia
721
+ // CORRETO para bloquear: 'requisite' deve RETORNAR null.
722
+ String authenticate(request){ if (!ok(request)) return null; return request.getParameter("username") }
723
+ ```
724
+
725
+ ### Anti-pattern 3 — emitir token de player via JSON e esperar whitelist de IP
726
+
727
+ ```
728
+ // O endpoint JSON não inclui a claim 'whitelist' no token (2.4),
729
+ // então a restrição de IP/domínio da Role NÃO é aplicada pelo SecurityFilter.
730
+ // Use form-urlencoded quando a whitelist for relevante para segurança.
731
+ ```
732
+
733
+ ---
734
+
735
+ ## Checklist de Configuração
736
+
737
+ - [ ] Role `player` existe em `security.roles` (sem ela, player sem Principal → NPE → 400 no login).
738
+ - [ ] `database` presente no scope da role se a gamificação usar `/v3/database`.
739
+ - [ ] `requirePassword` definido conforme a estratégia — lembrando que omitir o campo `password` contorna a checagem (5.1).
740
+ - [ ] `createPlayerIfDontExist` coerente com o cadastro de players (auto-criados não têm Principal).
741
+ - [ ] App registrado em `security.apps` (e `app_secret` **único** — duplicado quebra a autenticação, 5.5) para usar `client_credentials`.
742
+ - [ ] Auth Modules `requisite`/`sufficient` testados antes de produção — um script que lança exceção não bloqueia; para bloquear, retorne `null`.
743
+ - [ ] `module.timeout` ajustado para chamadas externas (o limite de loop continua fixo em 5s via `@TimedInterrupt`).
744
+ - [ ] Decisão consciente entre endpoint **form** (com refresh_token + whitelist) e **JSON** (sem ambos).
745
+ - [ ] `q` em `GET /v3/auth/module` nunca construído a partir de entrada não confiável (injeção NoSQL, 8.2).
88
746
 
89
- ### Configuração no Studio
747
+ ---
90
748
 
91
- 1. Acesse `Studio > Security`
92
- 2. Na seção **Roles**, clique no botão `+` para criar uma nova role
93
- 3. Defina: Role = `player`, Timeout = `1d`, Scope = `read_all, write_all, delete_all, database`
94
- 4. Clique em **Salvar**
95
- 5. O jogador precisa **fazer login novamente** para obter um token com as novas permissões
749
+ ## REGRAS DE OURO
96
750
 
97
- ## Validações e Testes
751
+ **NÃO faça:** inventar comportamento; assumir semântica REST/OAuth padrão sem confirmar no código; omitir inconsistências (NPE de role, bypass de escopo, injeção em `q`); simplificar o algoritmo PAM; ignorar código comentado (LDAP, campos legados).
98
752
 
99
- - [ ] Login retorna token válido
100
- - [ ] Token expira no tempo configurado
101
- - [ ] Jogador inválido recebe erro apropriado
102
- - [ ] SSO funciona (se configurado)
103
- - [ ] Role "player" está configurada com scope adequado
104
- - [ ] Token do jogador tem permissão para os endpoints necessários
753
+ **SEMPRE faça:** citar `arquivo:linha`; documentar campos removidos/ignorados silenciosamente; registrar a diferença entre o que o schema aceita e o que o runtime usa (claim `whitelist`, `optional` ignorado); indicar explicitamente o que **não** foi encontrado/implementado.