Begin support for external auths
[oweals/peertube.git] / server / lib / plugins / register-helpers-store.ts
1 import { logger } from '@server/helpers/logger'
2 import { VIDEO_CATEGORIES, VIDEO_LANGUAGES, VIDEO_LICENCES, VIDEO_PLAYLIST_PRIVACIES, VIDEO_PRIVACIES } from '@server/initializers/constants'
3 import { onExternalUserAuthenticated } from '@server/lib/auth'
4 import { PluginModel } from '@server/models/server/plugin'
5 import { RegisterServerOptions } from '@server/typings/plugins'
6 import { PluginPlaylistPrivacyManager } from '@shared/models/plugins/plugin-playlist-privacy-manager.model'
7 import { PluginSettingsManager } from '@shared/models/plugins/plugin-settings-manager.model'
8 import { PluginStorageManager } from '@shared/models/plugins/plugin-storage-manager.model'
9 import { PluginVideoCategoryManager } from '@shared/models/plugins/plugin-video-category-manager.model'
10 import { PluginVideoLanguageManager } from '@shared/models/plugins/plugin-video-language-manager.model'
11 import { PluginVideoLicenceManager } from '@shared/models/plugins/plugin-video-licence-manager.model'
12 import { PluginVideoPrivacyManager } from '@shared/models/plugins/plugin-video-privacy-manager.model'
13 import { RegisterServerAuthExternalOptions, RegisterServerAuthExternalResult, RegisterServerAuthPassOptions, RegisterServerExternalAuthenticatedResult } from '@shared/models/plugins/register-server-auth.model'
14 import { RegisterServerHookOptions } from '@shared/models/plugins/register-server-hook.model'
15 import { RegisterServerSettingOptions } from '@shared/models/plugins/register-server-setting.model'
16 import { serverHookObject } from '@shared/models/plugins/server-hook.model'
17 import * as express from 'express'
18 import { buildPluginHelpers } from './plugin-helpers'
19
20 type AlterableVideoConstant = 'language' | 'licence' | 'category' | 'privacy' | 'playlistPrivacy'
21 type VideoConstant = { [key in number | string]: string }
22
23 type UpdatedVideoConstant = {
24   [name in AlterableVideoConstant]: {
25     added: { key: number | string, label: string }[]
26     deleted: { key: number | string, label: string }[]
27   }
28 }
29
30 export class RegisterHelpersStore {
31   private readonly updatedVideoConstants: UpdatedVideoConstant = {
32     playlistPrivacy: { added: [], deleted: [] },
33     privacy: { added: [], deleted: [] },
34     language: { added: [], deleted: [] },
35     licence: { added: [], deleted: [] },
36     category: { added: [], deleted: [] }
37   }
38
39   private readonly settings: RegisterServerSettingOptions[] = []
40
41   private readonly idAndPassAuths: RegisterServerAuthPassOptions[] = []
42   private readonly externalAuths: RegisterServerAuthExternalOptions[] = []
43
44   private readonly router: express.Router
45
46   constructor (
47     private readonly npmName: string,
48     private readonly plugin: PluginModel,
49     private readonly onHookAdded: (options: RegisterServerHookOptions) => void
50   ) {
51     this.router = express.Router()
52   }
53
54   buildRegisterHelpers (): RegisterServerOptions {
55     const registerHook = this.buildRegisterHook()
56     const registerSetting = this.buildRegisterSetting()
57
58     const getRouter = this.buildGetRouter()
59
60     const settingsManager = this.buildSettingsManager()
61     const storageManager = this.buildStorageManager()
62
63     const videoLanguageManager = this.buildVideoLanguageManager()
64
65     const videoLicenceManager = this.buildVideoLicenceManager()
66     const videoCategoryManager = this.buildVideoCategoryManager()
67
68     const videoPrivacyManager = this.buildVideoPrivacyManager()
69     const playlistPrivacyManager = this.buildPlaylistPrivacyManager()
70
71     const registerIdAndPassAuth = this.buildRegisterIdAndPassAuth()
72     const registerExternalAuth = this.buildRegisterExternalAuth()
73
74     const peertubeHelpers = buildPluginHelpers(this.npmName)
75
76     return {
77       registerHook,
78       registerSetting,
79
80       getRouter,
81
82       settingsManager,
83       storageManager,
84
85       videoLanguageManager,
86       videoCategoryManager,
87       videoLicenceManager,
88
89       videoPrivacyManager,
90       playlistPrivacyManager,
91
92       registerIdAndPassAuth,
93       registerExternalAuth,
94
95       peertubeHelpers
96     }
97   }
98
99   reinitVideoConstants (npmName: string) {
100     const hash = {
101       language: VIDEO_LANGUAGES,
102       licence: VIDEO_LICENCES,
103       category: VIDEO_CATEGORIES,
104       privacy: VIDEO_PRIVACIES,
105       playlistPrivacy: VIDEO_PLAYLIST_PRIVACIES
106     }
107     const types: AlterableVideoConstant[] = [ 'language', 'licence', 'category', 'privacy', 'playlistPrivacy' ]
108
109     for (const type of types) {
110       const updatedConstants = this.updatedVideoConstants[type][npmName]
111       if (!updatedConstants) continue
112
113       for (const added of updatedConstants.added) {
114         delete hash[type][added.key]
115       }
116
117       for (const deleted of updatedConstants.deleted) {
118         hash[type][deleted.key] = deleted.label
119       }
120
121       delete this.updatedVideoConstants[type][npmName]
122     }
123   }
124
125   getSettings () {
126     return this.settings
127   }
128
129   getRouter () {
130     return this.router
131   }
132
133   getIdAndPassAuths () {
134     return this.idAndPassAuths
135   }
136
137   getExternalAuths () {
138     return this.externalAuths
139   }
140
141   private buildGetRouter () {
142     return () => this.router
143   }
144
145   private buildRegisterSetting () {
146     return (options: RegisterServerSettingOptions) => {
147       this.settings.push(options)
148     }
149   }
150
151   private buildRegisterHook () {
152     return (options: RegisterServerHookOptions) => {
153       if (serverHookObject[options.target] !== true) {
154         logger.warn('Unknown hook %s of plugin %s. Skipping.', options.target, this.npmName)
155         return
156       }
157
158       return this.onHookAdded(options)
159     }
160   }
161
162   private buildRegisterIdAndPassAuth () {
163     return (options: RegisterServerAuthPassOptions) => {
164       if (!options.authName || typeof options.getWeight !== 'function' || typeof options.login !== 'function') {
165         logger.error('Cannot register auth plugin %s: authName of getWeight or login are not valid.', this.npmName)
166         return
167       }
168
169       this.idAndPassAuths.push(options)
170     }
171   }
172
173   private buildRegisterExternalAuth () {
174     const self = this
175
176     return (options: RegisterServerAuthExternalOptions) => {
177       this.externalAuths.push(options)
178
179       return {
180         userAuthenticated (result: RegisterServerExternalAuthenticatedResult): void {
181           onExternalUserAuthenticated({
182             npmName: self.npmName,
183             authName: options.authName,
184             authResult: result
185           }).catch(err => {
186             logger.error('Cannot execute onExternalUserAuthenticated.', { npmName: self.npmName, authName: options.authName, err })
187           })
188         }
189       } as RegisterServerAuthExternalResult
190     }
191   }
192
193   private buildSettingsManager (): PluginSettingsManager {
194     return {
195       getSetting: (name: string) => PluginModel.getSetting(this.plugin.name, this.plugin.type, name),
196
197       getSettings: (names: string[]) => PluginModel.getSettings(this.plugin.name, this.plugin.type, names),
198
199       setSetting: (name: string, value: string) => PluginModel.setSetting(this.plugin.name, this.plugin.type, name, value)
200     }
201   }
202
203   private buildStorageManager (): PluginStorageManager {
204     return {
205       getData: (key: string) => PluginModel.getData(this.plugin.name, this.plugin.type, key),
206
207       storeData: (key: string, data: any) => PluginModel.storeData(this.plugin.name, this.plugin.type, key, data)
208     }
209   }
210
211   private buildVideoLanguageManager (): PluginVideoLanguageManager {
212     return {
213       addLanguage: (key: string, label: string) => {
214         return this.addConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key, label })
215       },
216
217       deleteLanguage: (key: string) => {
218         return this.deleteConstant({ npmName: this.npmName, type: 'language', obj: VIDEO_LANGUAGES, key })
219       }
220     }
221   }
222
223   private buildVideoCategoryManager (): PluginVideoCategoryManager {
224     return {
225       addCategory: (key: number, label: string) => {
226         return this.addConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key, label })
227       },
228
229       deleteCategory: (key: number) => {
230         return this.deleteConstant({ npmName: this.npmName, type: 'category', obj: VIDEO_CATEGORIES, key })
231       }
232     }
233   }
234
235   private buildVideoPrivacyManager (): PluginVideoPrivacyManager {
236     return {
237       deletePrivacy: (key: number) => {
238         return this.deleteConstant({ npmName: this.npmName, type: 'privacy', obj: VIDEO_PRIVACIES, key })
239       }
240     }
241   }
242
243   private buildPlaylistPrivacyManager (): PluginPlaylistPrivacyManager {
244     return {
245       deletePlaylistPrivacy: (key: number) => {
246         return this.deleteConstant({ npmName: this.npmName, type: 'playlistPrivacy', obj: VIDEO_PLAYLIST_PRIVACIES, key })
247       }
248     }
249   }
250
251   private buildVideoLicenceManager (): PluginVideoLicenceManager {
252     return {
253       addLicence: (key: number, label: string) => {
254         return this.addConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key, label })
255       },
256
257       deleteLicence: (key: number) => {
258         return this.deleteConstant({ npmName: this.npmName, type: 'licence', obj: VIDEO_LICENCES, key })
259       }
260     }
261   }
262
263   private addConstant<T extends string | number> (parameters: {
264     npmName: string
265     type: AlterableVideoConstant
266     obj: VideoConstant
267     key: T
268     label: string
269   }) {
270     const { npmName, type, obj, key, label } = parameters
271
272     if (obj[key]) {
273       logger.warn('Cannot add %s %s by plugin %s: key already exists.', type, npmName, key)
274       return false
275     }
276
277     if (!this.updatedVideoConstants[type][npmName]) {
278       this.updatedVideoConstants[type][npmName] = {
279         added: [],
280         deleted: []
281       }
282     }
283
284     this.updatedVideoConstants[type][npmName].added.push({ key, label })
285     obj[key] = label
286
287     return true
288   }
289
290   private deleteConstant<T extends string | number> (parameters: {
291     npmName: string
292     type: AlterableVideoConstant
293     obj: VideoConstant
294     key: T
295   }) {
296     const { npmName, type, obj, key } = parameters
297
298     if (!obj[key]) {
299       logger.warn('Cannot delete %s %s by plugin %s: key does not exist.', type, npmName, key)
300       return false
301     }
302
303     if (!this.updatedVideoConstants[type][npmName]) {
304       this.updatedVideoConstants[type][npmName] = {
305         added: [],
306         deleted: []
307       }
308     }
309
310     this.updatedVideoConstants[type][npmName].deleted.push({ key, label: obj[key] })
311     delete obj[key]
312
313     return true
314   }
315 }