@@ -13,32 +13,74 @@ app.use(express.json());
1313// Configurações principais
1414const PORT = process . env . PORT || 3000 ;
1515const PORTAINER_URL = process . env . PORTAINER_URL || 'http://localhost:9000' ;
16- const PORTAINER_API_KEY = process . env . PORTAINER_API_KEY || '' ;
17- const PORTAINER_JWT = process . env . PORTAINER_JWT || '' ;
16+ const PORTAINER_USERNAME = process . env . PORTAINER_USERNAME || 'admin ' ;
17+ const PORTAINER_PASSWORD = process . env . PORTAINER_PASSWORD || '' ;
1818const PORTAINER_ENDPOINT_ID = parseInt ( process . env . PORTAINER_ENDPOINT_ID ) || 1 ;
1919const AUTH_TOKEN = process . env . AUTH_TOKEN ;
2020const DOMAIN = process . env . DOMAIN ;
2121
2222const httpsAgent = new https . Agent ( { rejectUnauthorized : false } ) ;
2323
24- // ✅ Função para obter headers de autenticação do Portainer
25- const getPortainerHeaders = ( ) => {
26- // Prioriza JWT se disponível, senão usa API Key
27- if ( PORTAINER_JWT ) {
28- return {
29- 'Authorization' : `Bearer ${ PORTAINER_JWT } ` ,
30- 'Content-Type' : 'application/json'
31- } ;
32- } else if ( PORTAINER_API_KEY ) {
33- return {
34- 'X-API-Key' : PORTAINER_API_KEY ,
35- 'Content-Type' : 'application/json'
24+ // Cache do JWT (em memória)
25+ let jwtCache = {
26+ token : null ,
27+ expiresAt : null
28+ } ;
29+
30+ // ✅ Função para autenticar no Portainer e obter JWT
31+ const authenticatePortainer = async ( ) => {
32+ try {
33+ console . log ( '🔐 Autenticando no Portainer...' ) ;
34+
35+ const response = await axios . post (
36+ `${ PORTAINER_URL } /api/auth` ,
37+ {
38+ username : PORTAINER_USERNAME ,
39+ password : PORTAINER_PASSWORD
40+ } ,
41+ {
42+ headers : { 'Content-Type' : 'application/json' } ,
43+ httpsAgent
44+ }
45+ ) ;
46+
47+ const jwt = response . data . jwt ;
48+
49+ // Cache do token por 8 horas (padrão do Portainer)
50+ jwtCache = {
51+ token : jwt ,
52+ expiresAt : Date . now ( ) + ( 8 * 60 * 60 * 1000 )
3653 } ;
37- } else {
38- throw new Error ( 'Nenhuma credencial do Portainer configurada (JWT ou API Key)' ) ;
54+
55+ console . log ( '✅ Autenticação bem-sucedida' ) ;
56+ return jwt ;
57+
58+ } catch ( error ) {
59+ console . error ( '❌ Erro ao autenticar no Portainer:' , error . response ?. data || error . message ) ;
60+ throw new Error ( 'Falha na autenticação do Portainer' ) ;
3961 }
4062} ;
4163
64+ // ✅ Função para obter JWT válido (usa cache ou renova)
65+ const getValidJWT = async ( ) => {
66+ // Se tem token em cache e ainda é válido
67+ if ( jwtCache . token && jwtCache . expiresAt > Date . now ( ) ) {
68+ return jwtCache . token ;
69+ }
70+
71+ // Caso contrário, autentica novamente
72+ return await authenticatePortainer ( ) ;
73+ } ;
74+
75+ // ✅ Função para obter headers com JWT válido
76+ const getPortainerHeaders = async ( ) => {
77+ const jwt = await getValidJWT ( ) ;
78+ return {
79+ 'Authorization' : `Bearer ${ jwt } ` ,
80+ 'Content-Type' : 'application/json'
81+ } ;
82+ } ;
83+
4284// Middleware de autenticação da API
4385const authenticateToken = ( req , res , next ) => {
4486 const authHeader = req . headers [ 'authorization' ] ;
@@ -132,9 +174,11 @@ app.post('/api/stack', authenticateToken, async (req, res) => {
132174 } ) ;
133175 }
134176
135- // 1️⃣ Pegar Swarm ID do endpoint
177+ // 1️⃣ Obter headers com JWT válido
178+ const headers = await getPortainerHeaders ( ) ;
179+
180+ // 2️⃣ Pegar Swarm ID do endpoint
136181 console . log ( '📡 Buscando Swarm ID...' ) ;
137- const headers = getPortainerHeaders ( ) ;
138182
139183 const swarmResponse = await axios . get (
140184 `${ PORTAINER_URL } /api/endpoints/${ endpointId } /docker/swarm` ,
@@ -144,7 +188,7 @@ app.post('/api/stack', authenticateToken, async (req, res) => {
144188 const swarmId = swarmResponse . data . ID ;
145189 console . log ( '🆔 Swarm ID encontrado:' , swarmId ) ;
146190
147- // 2️⃣ Gera o template da stack
191+ // 3️⃣ Gera o template da stack
148192 const stackContent = getStackTemplate ( tipo , nome , rede , portaFinal ) ;
149193 console . log ( '📄 Template gerado para tipo:' , tipo ) ;
150194 console . log ( '🔌 Porta exposta:' , portaFinal ) ;
@@ -153,15 +197,15 @@ app.post('/api/stack', authenticateToken, async (req, res) => {
153197 ? `redis-${ nome } -${ portaFinal } `
154198 : nome ;
155199
156- // 3️⃣ Payload incluindo SwarmID
200+ // 4️⃣ Payload incluindo SwarmID
157201 const payload = {
158202 name : stackName ,
159203 stackFileContent : stackContent ,
160204 env : [ ] ,
161205 swarmID : swarmId
162206 } ;
163207
164- // 4️⃣ Criar stack
208+ // 5️⃣ Criar stack
165209 const url = `${ PORTAINER_URL } /api/stacks/create/swarm/string?endpointId=${ endpointId } ` ;
166210
167211 console . log ( '🔗 URL de criação:' , url ) ;
@@ -184,6 +228,13 @@ app.post('/api/stack', authenticateToken, async (req, res) => {
184228
185229 } catch ( error ) {
186230 console . error ( '❌ Erro ao criar stack' ) ;
231+
232+ // Se o erro for de autenticação, limpa o cache e tenta novamente
233+ if ( error . response ?. status === 401 ) {
234+ console . log ( '🔄 Token expirado, limpando cache...' ) ;
235+ jwtCache = { token : null , expiresAt : null } ;
236+ }
237+
187238 if ( error . response ) {
188239 console . error ( 'Status:' , error . response . status ) ;
189240 console . error ( 'Body da resposta:' , JSON . stringify ( error . response . data , null , 2 ) ) ;
@@ -201,7 +252,7 @@ app.post('/api/stack', authenticateToken, async (req, res) => {
201252// Endpoint para listar stacks
202253app . get ( '/api/stacks' , authenticateToken , async ( req , res ) => {
203254 try {
204- const headers = getPortainerHeaders ( ) ;
255+ const headers = await getPortainerHeaders ( ) ;
205256
206257 const response = await axios . get ( `${ PORTAINER_URL } /api/stacks` , {
207258 headers,
@@ -211,15 +262,29 @@ app.get('/api/stacks', authenticateToken, async (req, res) => {
211262 res . json ( { success : true , stacks : response . data } ) ;
212263 } catch ( error ) {
213264 console . error ( 'Erro ao listar stacks:' , error . response ?. data || error . message ) ;
265+
266+ // Se o erro for de autenticação, limpa o cache
267+ if ( error . response ?. status === 401 ) {
268+ jwtCache = { token : null , expiresAt : null } ;
269+ }
270+
214271 res . status ( error . response ?. status || 500 ) . json ( {
215272 error : 'Erro ao listar stacks' ,
216273 details : error . response ?. data || error . message
217274 } ) ;
218275 }
219276} ) ;
220277
278+
279+
221280// Health check
222- app . get ( '/health' , ( req , res ) => res . json ( { status : 'ok' , timestamp : new Date ( ) . toISOString ( ) } ) ) ;
281+ app . get ( '/health' , ( req , res ) => {
282+ res . json ( {
283+ status : 'ok' ,
284+ timestamp : new Date ( ) . toISOString ( ) ,
285+ portainerAuth : jwtCache . token ? 'authenticated' : 'not_authenticated'
286+ } ) ;
287+ } ) ;
223288
224289// Listar tipos
225290app . get ( '/api/tipos' , ( req , res ) => {
@@ -234,18 +299,68 @@ app.get('/api/tipos', (req, res) => {
234299 } ) ;
235300} ) ;
236301
302+ // Status da autenticação
303+ app . get ( '/api/auth/status' , authenticateToken , ( req , res ) => {
304+ res . json ( {
305+ authenticated : ! ! jwtCache . token ,
306+ expiresAt : jwtCache . expiresAt ? new Date ( jwtCache . expiresAt ) . toISOString ( ) : null ,
307+ timeRemaining : jwtCache . expiresAt ? Math . max ( 0 , jwtCache . expiresAt - Date . now ( ) ) : 0
308+ } ) ;
309+ } ) ;
310+
311+ // Forçar reautenticação
312+ app . post ( '/api/auth/refresh' , authenticateToken , async ( req , res ) => {
313+ try {
314+ jwtCache = { token : null , expiresAt : null } ;
315+ const jwt = await authenticatePortainer ( ) ;
316+
317+ res . json ( {
318+ success : true ,
319+ message : 'Autenticação renovada com sucesso' ,
320+ expiresAt : new Date ( jwtCache . expiresAt ) . toISOString ( )
321+ } ) ;
322+ } catch ( error ) {
323+ res . status ( 500 ) . json ( {
324+ error : 'Erro ao renovar autenticação' ,
325+ details : error . message
326+ } ) ;
327+ }
328+ } ) ;
329+
237330// Inicialização do servidor
238- app . listen ( PORT , ( ) => {
239- console . log ( `\n🌀 version: 1.2.0` ) ;
240- console . log ( `🚀 API rodando na porta ${ PORT } ` ) ;
241- console . log ( `📦 Portainer URL: ${ PORTAINER_URL } ` ) ;
242- console . log ( `🔑 Autenticação Portainer: ${ PORTAINER_JWT ? 'JWT' : PORTAINER_API_KEY ? 'API Key' : 'NENHUMA ⚠️' } ` ) ;
243- console . log ( `🌐 Endpoint ID padrão: ${ PORTAINER_ENDPOINT_ID } ` ) ;
244- console . log ( `🐳 Modo Docker: ${ process . env . DOCKER_ENV || false } ` ) ;
245- console . log ( `🔐 Auth Token API: ${ AUTH_TOKEN ? '✅' : '❌' } ` ) ;
246- console . log ( `\n📝 Endpoints disponíveis:` ) ;
247- console . log ( ` POST /api/stack - Criar stack` ) ;
248- console . log ( ` GET /api/stacks - Listar stacks` ) ;
249- console . log ( ` GET /api/tipos - Listar tipos disponíveis` ) ;
250- console . log ( ` GET /health - Health check` ) ;
251- } ) ;
331+ const startServer = async ( ) => {
332+ try {
333+ // Valida credenciais obrigatórias
334+ if ( ! PORTAINER_USERNAME || ! PORTAINER_PASSWORD ) {
335+ console . error ( '❌ ERRO: PORTAINER_USERNAME e PORTAINER_PASSWORD são obrigatórios!' ) ;
336+ process . exit ( 1 ) ;
337+ }
338+
339+ // Tenta autenticar no início
340+ await authenticatePortainer ( ) ;
341+
342+ app . listen ( PORT , ( ) => {
343+ console . log ( `\n🌀 version: 2.0.0` ) ;
344+ console . log ( `🚀 API rodando na porta ${ PORT } ` ) ;
345+ console . log ( `📦 Portainer URL: ${ PORTAINER_URL } ` ) ;
346+ console . log ( `👤 Usuário Portainer: ${ PORTAINER_USERNAME } ` ) ;
347+ console . log ( `🔐 Autenticação: JWT Automático ✅` ) ;
348+ console . log ( `🌐 Endpoint ID padrão: ${ PORTAINER_ENDPOINT_ID } ` ) ;
349+ console . log ( `🐳 Modo Docker: ${ process . env . DOCKER_ENV || false } ` ) ;
350+ console . log ( `🔐 Auth Token API: ${ AUTH_TOKEN ? '✅' : '❌' } ` ) ;
351+ console . log ( `\n📝 Endpoints disponíveis:` ) ;
352+ console . log ( ` POST /api/stack - Criar stack` ) ;
353+ console . log ( ` GET /api/stacks - Listar stacks` ) ;
354+ console . log ( ` GET /api/tipos - Listar tipos disponíveis` ) ;
355+ console . log ( ` GET /api/auth/status - Status da autenticação` ) ;
356+ console . log ( ` POST /api/auth/refresh - Renovar autenticação` ) ;
357+ console . log ( ` GET /health - Health check` ) ;
358+ } ) ;
359+
360+ } catch ( error ) {
361+ console . error ( '❌ Erro ao iniciar servidor:' , error . message ) ;
362+ process . exit ( 1 ) ;
363+ }
364+ } ;
365+
366+ startServer ( ) ;
0 commit comments