Possibility to configure an OpenID Connect provider on the instance level WIP (#128)
Pruning external users periodically.
This commit is contained in:
		| @ -7,6 +7,8 @@ module:depends"http"; | ||||
|  | ||||
| local module_host = module:get_host(); -- this module is not global | ||||
|  | ||||
| local bare_sessions = prosody.bare_sessions; | ||||
|  | ||||
| local vcards = module:open_store("vcard"); | ||||
|  | ||||
| function check_auth(routes) | ||||
| @ -113,10 +115,35 @@ local function ensure_user(event) | ||||
|   }); | ||||
| end | ||||
|  | ||||
| -- TODO: add a function to prune user that have not logged in since X days. | ||||
|  | ||||
| local function prune_users(event) -- delete all users that are not connected! | ||||
|   local request, response = event.request, event.response; | ||||
|   event.response.headers["Content-Type"] = "application/json"; | ||||
|  | ||||
|   module:log("info", "Calling prune_users for host %s", module_host); | ||||
|  | ||||
|   for user in usermanager.users(module_host) do | ||||
|     -- has the user still open sessions? | ||||
|     if (bare_sessions[user..'@'..module_host] ~= nil) then | ||||
|       module:log("debug", "User %s on host %s has still active sessions, ignoring.", user, module_host); | ||||
|     else | ||||
|       -- FIXME: there is a little chance that we delete a user that is currently connecting... | ||||
|       -- As this prune should not be called too often, we can consider it is not an issue for now. | ||||
|  | ||||
|       module:log("debug", "Deleting user %s on host %s", user, module_host); | ||||
|       update_vcard(user, nil); | ||||
|       usermanager.delete_user(user, module_host); | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   return json.encode({ | ||||
|     result = "ok"; | ||||
|   }); | ||||
| end | ||||
|  | ||||
| module:provides("http", { | ||||
|   route = check_auth { | ||||
|     ["POST /" .. module_host .. "/ensure-user"] = ensure_user; | ||||
|     ["POST /" .. module_host .. "/prune-users"] = prune_users; | ||||
|   }; | ||||
| }); | ||||
|  | ||||
| @ -19,6 +19,7 @@ interface DebugContent { | ||||
|   alwaysPublishXMPPRoom?: boolean | ||||
|   enablePodcastChatTagForNonLive?: boolean | ||||
|   mucAdmins?: string[] | ||||
|   externalAccountPruneInterval?: number | ||||
| } | ||||
|  | ||||
| type DebugNumericValue = 'renewCertCheckInterval' | ||||
| @ -26,6 +27,7 @@ type DebugNumericValue = 'renewCertCheckInterval' | ||||
| | 'logRotateEvery' | ||||
| | 'logRotateCheckInterval' | ||||
| | 'remoteServerInfosMaxAge' | ||||
| | 'externalAccountPruneInterval' | ||||
|  | ||||
| type DebugBooleanValue = 'alwaysPublishXMPPRoom' | 'enablePodcastChatTagForNonLive' | 'useOpenSSL' | ||||
|  | ||||
| @ -65,6 +67,7 @@ function _readDebugFile (options: RegisterServerOptions): DebugContent | false { | ||||
|     debugContent.alwaysPublishXMPPRoom = json.always_publish_xmpp_room === true | ||||
|     debugContent.enablePodcastChatTagForNonLive = json.enable_podcast_chat_tag_for_nonlive === true | ||||
|     debugContent.mucAdmins = _getJIDs(options, json, 'muc_admins') | ||||
|     debugContent.externalAccountPruneInterval = _getNumericOptions(options, json, 'external_account_prune_interval') | ||||
|   } catch (err) { | ||||
|     logger.error('Failed to read the debug_mode file content:', err) | ||||
|   } | ||||
|  | ||||
| @ -5,7 +5,9 @@ import { ExternalAuthenticationError } from './error' | ||||
| import { getBaseRouterRoute } from '../helpers' | ||||
| import { canonicalizePluginUri } from '../uri/canonicalize' | ||||
| import { getProsodyDomain } from '../prosody/config/domain' | ||||
| import { pruneUsers } from '../prosody/api/manage-users' | ||||
| import { getProsodyFilePaths } from '../prosody/config' | ||||
| import { debugNumericParameter } from '../debug' | ||||
| import { createCipheriv, createDecipheriv, randomBytes, Encoding } from 'node:crypto' | ||||
| import { Issuer, BaseClient, generators, UnknownObject } from 'openid-client' | ||||
| import { JID } from '@xmpp/jid' | ||||
| @ -80,6 +82,7 @@ class ExternalAuthOIDC { | ||||
|   private readonly externalVirtualhost: string | ||||
|   private readonly avatarsDir: string | ||||
|   private readonly avatarsFiles: string[] | ||||
|   private pruneTimer?: NodeJS.Timer | ||||
|  | ||||
|   private readonly encryptionOptions = { | ||||
|     algorithm: 'aes256' as string, | ||||
| @ -619,11 +622,45 @@ class ExternalAuthOIDC { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Starts an interval timer to prune external users from Prosody. | ||||
|    * @param options Peertube server options. | ||||
|    */ | ||||
|   public startPruneTimer (options: RegisterServerOptions): void { | ||||
|     this.stopPruneTimer() // just in case... | ||||
|  | ||||
|     // every 4 hour (every minutes in debug mode) | ||||
|     const pruneInterval = debugNumericParameter(options, 'externalAccountPruneInterval', 60 * 1000, 4 * 60 * 60 * 1000) | ||||
|     this.logger.info(`Creating a timer for external account pruning, every ${Math.round(pruneInterval / 1000)}s.`) | ||||
|  | ||||
|     // eslint-disable-next-line @typescript-eslint/no-misused-promises | ||||
|     this.pruneTimer = setInterval(async () => { | ||||
|       try { | ||||
|         if (!await this.isOk()) { return } | ||||
|  | ||||
|         this.logger.info('Pruning external users...') | ||||
|         await pruneUsers(options) | ||||
|       } catch (err) { | ||||
|         this.logger.error('Error while pruning external users: ' + (err as string)) | ||||
|       } | ||||
|     }, pruneInterval) | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Stops the prune timer. | ||||
|    */ | ||||
|   public stopPruneTimer (): void { | ||||
|     if (!this.pruneTimer) { return } | ||||
|     clearInterval(this.pruneTimer) | ||||
|     this.pruneTimer = undefined | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * frees the singleton | ||||
|    */ | ||||
|   public static async destroySingleton (): Promise<void> { | ||||
|     if (!singleton) { return } | ||||
|     singleton.stopPruneTimer() | ||||
|     singleton = undefined | ||||
|   } | ||||
|  | ||||
| @ -663,6 +700,8 @@ class ExternalAuthOIDC { | ||||
|       avatarsFiles: prosodyFilePaths.avatarsFiles | ||||
|     }) | ||||
|  | ||||
|     singleton.startPruneTimer(options) | ||||
|  | ||||
|     return singleton | ||||
|   } | ||||
|  | ||||
|  | ||||
| @ -62,6 +62,47 @@ async function ensureUser (options: RegisterServerOptions, infos: ExternalAccoun | ||||
|   return true | ||||
| } | ||||
|  | ||||
| export { | ||||
|   ensureUser | ||||
| /** | ||||
|  * Calls an API provided by mod_http_peertubelivechat_manage_users, to prune unused users. | ||||
|  * @param options Peertube server options | ||||
|  * @throws Error | ||||
|  */ | ||||
| async function pruneUsers (options: RegisterServerOptions): Promise<void> { | ||||
|   const logger = options.peertubeHelpers.logger | ||||
|  | ||||
|   const currentProsody = getCurrentProsody() | ||||
|   if (!currentProsody) { | ||||
|     throw new Error('It seems that prosody is not binded... Cant call API.') | ||||
|   } | ||||
|  | ||||
|   const prosodyDomain = await getProsodyDomain(options) | ||||
|  | ||||
|   logger.info('Calling pruneUsers') | ||||
|  | ||||
|   // Requesting on localhost, because currentProsody.host does not always resolves correctly (docker use case, ...) | ||||
|   const apiUrl = `http://localhost:${currentProsody.port}/` + | ||||
|     'peertubelivechat_manage_users/' + | ||||
|     `external.${prosodyDomain}/` + // the virtual host name | ||||
|     'prune-users' | ||||
|  | ||||
|   try { | ||||
|     logger.debug('Calling prune-users API on url: ' + apiUrl) | ||||
|     await got(apiUrl, { | ||||
|       method: 'POST', | ||||
|       headers: { | ||||
|         authorization: 'Bearer ' + await getAPIKey(options), | ||||
|         host: currentProsody.host | ||||
|       }, | ||||
|       json: {}, | ||||
|       responseType: 'json', | ||||
|       resolveBodyOnly: true | ||||
|     }) | ||||
|   } catch (err) { | ||||
|     logger.error(`prune-users failed: ' ${err as string}`) | ||||
|   } | ||||
| } | ||||
|  | ||||
| export { | ||||
|   ensureUser, | ||||
|   pruneUsers | ||||
| } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user