auth.js 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. // Zoho OAuth2 Authentication Module
  2. // EU data center: accounts.zoho.eu
  3. var ACCOUNTS_BASE = 'https://accounts.zoho.eu';
  4. var SCOPE = 'ZohoProjects.tasks.ALL,ZohoProjects.projects.READ,ZohoProjects.tasklists.READ';
  5. function setupAuth(grantToken) {
  6. var clientId = shadowman.config.value('client_id');
  7. var clientSecret = shadowman.config.value('client_secret');
  8. if (!clientId || !clientSecret) {
  9. return { error: 'Missing OAuth credentials. Set client_id and client_secret in plugin config.' };
  10. }
  11. if (!grantToken) {
  12. return { error: 'Missing grant_token parameter. Generate one from Zoho Developer Console (Self Client).' };
  13. }
  14. shadowman.log.info('Exchanging Zoho grant token for access + refresh tokens...');
  15. // Exchange grant token for access + refresh tokens
  16. // POST /oauth/v2/token with grant_type=authorization_code
  17. var body = 'grant_type=authorization_code' +
  18. '&client_id=' + encodeURIComponent(clientId) +
  19. '&client_secret=' + encodeURIComponent(clientSecret) +
  20. '&redirect_uri=oob' +
  21. '&code=' + encodeURIComponent(grantToken);
  22. var resp = shadowman.http.post(ACCOUNTS_BASE + '/oauth/v2/token', body, {
  23. headers: {
  24. 'Content-Type': 'application/x-www-form-urlencoded'
  25. }
  26. });
  27. if (resp.status !== 200) {
  28. shadowman.log.error('Token exchange failed: ' + resp.status + ' ' + resp.body);
  29. var errBody;
  30. try {
  31. errBody = JSON.parse(resp.body);
  32. } catch (e) {
  33. errBody = resp.body;
  34. }
  35. return { error: 'Token exchange failed: ' + (errBody.error || errBody.error_description || resp.body) };
  36. }
  37. var tokens;
  38. try {
  39. tokens = JSON.parse(resp.body);
  40. } catch (e) {
  41. return { error: 'Invalid response from Zoho: ' + resp.body };
  42. }
  43. // Store tokens with expiry calculation
  44. shadowman.storage.set('tokens', {
  45. access_token: tokens.access_token,
  46. refresh_token: tokens.refresh_token,
  47. expires_at: Date.now() + (tokens.expires_in * 1000),
  48. api_domain: tokens.api_domain || 'https://projectsapi.zoho.eu',
  49. token_type: tokens.token_type || 'Bearer'
  50. });
  51. shadowman.log.info('Zoho OAuth tokens stored successfully. Refresh token does not expire.');
  52. // Notify conversation
  53. var convId = shadowman.storage.get('auth_conversation_id');
  54. if (convId) {
  55. shadowman.events.emit('message', {
  56. text: 'Zoho OAuth sikeres! A refresh token nem jár le, az access token automatikusan frissül. Most már használhatod a Zoho Tasks funkciókat.',
  57. conversationId: convId
  58. });
  59. shadowman.storage.del('auth_conversation_id');
  60. }
  61. return {
  62. success: true,
  63. message: 'Authentication successful. Refresh token stored permanently.',
  64. expires_in: tokens.expires_in
  65. };
  66. }
  67. function getStatus() {
  68. var tokens = shadowman.storage.get('tokens');
  69. if (!tokens) {
  70. return { authenticated: false, message: 'Not authenticated. Use setup_auth with a grant token.' };
  71. }
  72. if (Date.now() >= tokens.expires_at) {
  73. // Token expired, try refresh
  74. var newTokens = refreshTokenInternal();
  75. if (newTokens.error) {
  76. return { authenticated: false, message: 'Token expired and refresh failed. Re-run setup_auth.' };
  77. }
  78. return { authenticated: true, message: 'Authenticated (token refreshed)', domain: newTokens.api_domain };
  79. }
  80. var remaining = Math.round((tokens.expires_at - Date.now()) / 1000);
  81. return {
  82. authenticated: true,
  83. message: 'Authenticated',
  84. expires_in_seconds: remaining,
  85. domain: tokens.api_domain
  86. };
  87. }
  88. function getValidToken() {
  89. var tokens = shadowman.storage.get('tokens');
  90. if (!tokens) {
  91. return null;
  92. }
  93. // Check if token needs refresh (5 minute buffer)
  94. if (Date.now() >= (tokens.expires_at - 300000)) {
  95. var newTokens = refreshTokenInternal();
  96. if (newTokens.error) {
  97. shadowman.log.error('Token refresh failed: ' + newTokens.error);
  98. return null;
  99. }
  100. return newTokens.access_token;
  101. }
  102. return tokens.access_token;
  103. }
  104. function refreshTokenInternal() {
  105. var tokens = shadowman.storage.get('tokens');
  106. if (!tokens || !tokens.refresh_token) {
  107. return { error: 'No refresh token available' };
  108. }
  109. var clientId = shadowman.config.value('client_id');
  110. var clientSecret = shadowman.config.value('client_secret');
  111. if (!clientId || !clientSecret) {
  112. return { error: 'OAuth client credentials missing' };
  113. }
  114. shadowman.log.info('Refreshing Zoho access token...');
  115. var resp = shadowman.http.post(ACCOUNTS_BASE + '/oauth/v2/token', null, {
  116. headers: {
  117. 'Content-Type': 'application/x-www-form-urlencoded'
  118. },
  119. body: 'grant_type=refresh_token' +
  120. '&client_id=' + encodeURIComponent(clientId) +
  121. '&client_secret=' + encodeURIComponent(clientSecret) +
  122. '&refresh_token=' + encodeURIComponent(tokens.refresh_token)
  123. });
  124. if (resp.status !== 200) {
  125. shadowman.log.error('Token refresh failed: ' + resp.status + ' ' + resp.body);
  126. return { error: 'Refresh failed: ' + resp.body };
  127. }
  128. var newTokens;
  129. try {
  130. newTokens = JSON.parse(resp.body);
  131. } catch (e) {
  132. return { error: 'Invalid refresh response: ' + resp.body };
  133. }
  134. var updated = {
  135. access_token: newTokens.access_token,
  136. refresh_token: tokens.refresh_token, // Keep original - Zoho refresh tokens don't expire
  137. expires_at: Date.now() + (newTokens.expires_in * 1000),
  138. api_domain: newTokens.api_domain || tokens.api_domain,
  139. token_type: newTokens.token_type || 'Bearer'
  140. };
  141. shadowman.storage.set('tokens', updated);
  142. shadowman.log.info('Zoho token refreshed successfully');
  143. return updated;
  144. }
  145. module.exports = {
  146. setupAuth: setupAuth,
  147. getStatus: getStatus,
  148. getValidToken: getValidToken
  149. };