Adding some standard OpenID Connect providers (Google, Facebook) (WIP):
* refactoring, to allow several OIDC singletons * settings for google and facebook * backend code
This commit is contained in:
parent
4bc2d4fd51
commit
024186ba2c
@ -15,6 +15,7 @@ TODO: https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/48
|
|||||||
* For anonymous users: new "log in using an external account" dialog, with following options:
|
* For anonymous users: new "log in using an external account" dialog, with following options:
|
||||||
* remote Peertube account,
|
* remote Peertube account,
|
||||||
* #128, #363 (**Experimental Feature**): possibility to configure an OpenID Connect provider on the instance level.
|
* #128, #363 (**Experimental Feature**): possibility to configure an OpenID Connect provider on the instance level.
|
||||||
|
* #128: adding some standard OpenID Connect providers (Google, Facebook).
|
||||||
* #143: User colors: implementing [XEP-0392](https://xmpp.org/extensions/xep-0392.html) to have random colors on users nicknames
|
* #143: User colors: implementing [XEP-0392](https://xmpp.org/extensions/xep-0392.html) to have random colors on users nicknames
|
||||||
* #330: Chat does no more use an iframe to display the chat besides the videos.
|
* #330: Chat does no more use an iframe to display the chat besides the videos.
|
||||||
* #330: Fullscreen chat: now uses a custom page (in other words: when opening the chat in a new tab, you will have the Peertube menu).
|
* #330: Fullscreen chat: now uses a custom page (in other words: when opening the chat in a new tab, you will have the Peertube menu).
|
||||||
|
@ -236,8 +236,11 @@ function register (clientOptions: RegisterClientOptions): void {
|
|||||||
return options.formValues['chat-no-anonymous'] !== false
|
return options.formValues['chat-no-anonymous'] !== false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (name?.startsWith('external-auth-custom-oidc-')) {
|
if (name?.startsWith('external-auth-')) {
|
||||||
return options.formValues['external-auth-custom-oidc'] !== true
|
const m = name.match(/^external-auth-(\w+)-oidc-/)
|
||||||
|
if (m) {
|
||||||
|
return options.formValues['external-auth-' + m[1] + '-oidc'] !== true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
|
@ -401,8 +401,8 @@ login_using_external_account: Mit einem externem Account anmelden
|
|||||||
external_auth_custom_oidc_label: Verwenden eines OpenID Connect Anbieters
|
external_auth_custom_oidc_label: Verwenden eines OpenID Connect Anbieters
|
||||||
external_auth_custom_oidc_button_label_label: Name für die Anmeldungsschaltfläche
|
external_auth_custom_oidc_button_label_label: Name für die Anmeldungsschaltfläche
|
||||||
external_auth_custom_oidc_discovery_url_label: Discovery URL
|
external_auth_custom_oidc_discovery_url_label: Discovery URL
|
||||||
external_auth_custom_oidc_client_id_label: Client ID
|
external_auth_oidc_client_id_label: Client ID
|
||||||
external_auth_custom_oidc_client_secret_label: Client secret
|
external_auth_oidc_client_secret_label: Client secret
|
||||||
external_auth_custom_oidc_title: <h4>OpenID Connect</h4>
|
external_auth_custom_oidc_title: <h4>OpenID Connect</h4>
|
||||||
external_auth_custom_oidc_button_label_description: Diese Bezeichnung wird den Nutzern
|
external_auth_custom_oidc_button_label_description: Diese Bezeichnung wird den Nutzern
|
||||||
als Name der Schaltfläche zur Authentifizierung bei diesem OIDC-Anbieter angezeigt.
|
als Name der Schaltfläche zur Authentifizierung bei diesem OIDC-Anbieter angezeigt.
|
||||||
@ -414,6 +414,6 @@ external_auth_description: "<h3>Externe Authentifizierung</h3>\nFür Benutzer, d
|
|||||||
kein Peertubekonto haben, können Sie verschiedene Authentifizierungsmodi auf der
|
kein Peertubekonto haben, können Sie verschiedene Authentifizierungsmodi auf der
|
||||||
Grundlage von externen Authentifizierungsanbietern aktivieren.\n"
|
Grundlage von externen Authentifizierungsanbietern aktivieren.\n"
|
||||||
login_external_auth_alert_message: Authentifizierung fehlgeschlagen
|
login_external_auth_alert_message: Authentifizierung fehlgeschlagen
|
||||||
external_auth_custom_oidc_redirect_uris_info_description: "<strong>Callback/Redirect
|
external_auth_oidc_redirect_uris_info_description: "<strong>Callback/Redirect
|
||||||
URI:</strong>\nWenn Sie eine autorisierte Umleitungs-URI für die externe Anwendung
|
URI:</strong>\nWenn Sie eine autorisierte Umleitungs-URI für die externe Anwendung
|
||||||
konfigurieren möchten, fügen Sie bitte diese URL hinzu:\n"
|
konfigurieren möchten, fügen Sie bitte diese URL hinzu:\n"
|
||||||
|
@ -83,12 +83,21 @@ external_auth_custom_oidc_button_label_label: "Label for the connection button"
|
|||||||
external_auth_custom_oidc_button_label_description: "This label will be displayed to users, as the button label to authenticate with this OIDC provider."
|
external_auth_custom_oidc_button_label_description: "This label will be displayed to users, as the button label to authenticate with this OIDC provider."
|
||||||
|
|
||||||
external_auth_custom_oidc_discovery_url_label: "Discovery URL"
|
external_auth_custom_oidc_discovery_url_label: "Discovery URL"
|
||||||
external_auth_custom_oidc_client_id_label: "Client ID"
|
external_auth_oidc_client_id_label: "Client ID"
|
||||||
external_auth_custom_oidc_client_secret_label: "Client secret"
|
external_auth_oidc_client_secret_label: "Client secret"
|
||||||
external_auth_custom_oidc_redirect_uris_info_description: |
|
external_auth_oidc_redirect_uris_info_description: |
|
||||||
<strong>Callback/Redirect URI:</strong>
|
<strong>Callback/Redirect URI:</strong>
|
||||||
If you want to configure authorized redirection URI on the external Application, please add this url:
|
If you want to configure authorized redirection URI on the external Application, please add this url:
|
||||||
|
|
||||||
|
external_auth_google_oidc_label: 'Use Google'
|
||||||
|
external_auth_google_oidc_description: |
|
||||||
|
Enabling this adds a "login with Google" button.
|
||||||
|
You have to configure a Google OAuth application.
|
||||||
|
external_auth_facebook_oidc_label: 'Use Facebook'
|
||||||
|
external_auth_facebook_oidc_description: |
|
||||||
|
Enabling this adds a "login with Facebook" button.
|
||||||
|
You have to configure a Facebook OAuth application.
|
||||||
|
|
||||||
chat_behaviour_description: "<h3>Chat behaviour</h3>"
|
chat_behaviour_description: "<h3>Chat behaviour</h3>"
|
||||||
|
|
||||||
room_type_label: "Room type"
|
room_type_label: "Room type"
|
||||||
|
@ -2,8 +2,8 @@ login_remote_peertube_video_not_found_try_anyway_button: Svejedno pokušaj otvor
|
|||||||
video na Peertube instanci
|
video na Peertube instanci
|
||||||
external_auth_custom_oidc_button_label_label: Oznaka za gumb povezivanja
|
external_auth_custom_oidc_button_label_label: Oznaka za gumb povezivanja
|
||||||
external_auth_custom_oidc_discovery_url_label: Discovery URL
|
external_auth_custom_oidc_discovery_url_label: Discovery URL
|
||||||
external_auth_custom_oidc_client_id_label: ID klijenta
|
external_auth_oidc_client_id_label: ID klijenta
|
||||||
external_auth_custom_oidc_client_secret_label: Tajna klijenta
|
external_auth_oidc_client_secret_label: Tajna klijenta
|
||||||
videos_list_label: Aktiviraj chat za ova videa
|
videos_list_label: Aktiviraj chat za ova videa
|
||||||
prosody_peertube_uri_label: Peertube URL za API pozive
|
prosody_peertube_uri_label: Peertube URL za API pozive
|
||||||
save: Spremi
|
save: Spremi
|
||||||
|
@ -85,10 +85,10 @@ async function getConverseJSParams (
|
|||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const oidc = ExternalAuthOIDC.singleton()
|
const customOidc = ExternalAuthOIDC.singleton('custom')
|
||||||
if (await oidc.isOk()) {
|
if (await customOidc.isOk()) {
|
||||||
const authUrl = oidc.getConnectUrl()
|
const authUrl = customOidc.getConnectUrl()
|
||||||
const buttonLabel = oidc.getButtonLabel()
|
const buttonLabel = customOidc.getButtonLabel()
|
||||||
if (authUrl && buttonLabel) {
|
if (authUrl && buttonLabel) {
|
||||||
externalAuthOIDC = {
|
externalAuthOIDC = {
|
||||||
buttonLabel: buttonLabel,
|
buttonLabel: buttonLabel,
|
||||||
|
@ -1,14 +1,19 @@
|
|||||||
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||||
import { newResult, TestResult } from './utils'
|
import { newResult, TestResult } from './utils'
|
||||||
import { ExternalAuthOIDC } from '../external-auth/oidc'
|
import { ExternalAuthOIDC, ExternalAuthOIDCType } from '../external-auth/oidc'
|
||||||
|
|
||||||
export async function diagExternalAuthCustomOIDC (test: string, _options: RegisterServerOptions): Promise<TestResult> {
|
export async function diagExternalAuthOIDC (
|
||||||
|
test: string,
|
||||||
|
_options: RegisterServerOptions,
|
||||||
|
singletonType: ExternalAuthOIDCType,
|
||||||
|
next: TestResult['next']
|
||||||
|
): Promise<TestResult> {
|
||||||
const result = newResult(test)
|
const result = newResult(test)
|
||||||
result.label = 'Test External Auth Custom OIDC'
|
result.label = 'Test External Auth OIDC: ' + singletonType
|
||||||
result.next = 'everything-ok'
|
result.next = next
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const oidc = ExternalAuthOIDC.singleton()
|
const oidc = ExternalAuthOIDC.singleton(singletonType)
|
||||||
|
|
||||||
if (oidc.isDisabledBySettings()) {
|
if (oidc.isDisabledBySettings()) {
|
||||||
result.ok = true
|
result.ok = true
|
||||||
@ -40,7 +45,7 @@ export async function diagExternalAuthCustomOIDC (test: string, _options: Regist
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const oidc = ExternalAuthOIDC.singleton()
|
const oidc = ExternalAuthOIDC.singleton(singletonType)
|
||||||
const oidcClient = await oidc.load()
|
const oidcClient = await oidc.load()
|
||||||
if (oidcClient) {
|
if (oidcClient) {
|
||||||
result.messages.push('Discovery URL loaded: ' + JSON.stringify(oidcClient.issuer.metadata))
|
result.messages.push('Discovery URL loaded: ' + JSON.stringify(oidcClient.issuer.metadata))
|
@ -4,7 +4,7 @@ import { TestResult, newResult } from './utils'
|
|||||||
import { diagDebug } from './debug'
|
import { diagDebug } from './debug'
|
||||||
import { diagProsody } from './prosody'
|
import { diagProsody } from './prosody'
|
||||||
import { diagVideo } from './video'
|
import { diagVideo } from './video'
|
||||||
import { diagExternalAuthCustomOIDC } from './external-auth-custom-oidc'
|
import { diagExternalAuthOIDC } from './external-auth-oidc'
|
||||||
import { helpUrl } from '../../../shared/lib/help'
|
import { helpUrl } from '../../../shared/lib/help'
|
||||||
|
|
||||||
export async function diag (test: string, options: RegisterServerOptions): Promise<TestResult> {
|
export async function diag (test: string, options: RegisterServerOptions): Promise<TestResult> {
|
||||||
@ -19,7 +19,11 @@ export async function diag (test: string, options: RegisterServerOptions): Promi
|
|||||||
} else if (test === 'prosody') {
|
} else if (test === 'prosody') {
|
||||||
result = await diagProsody(test, options)
|
result = await diagProsody(test, options)
|
||||||
} else if (test === 'external-auth-custom-oidc') {
|
} else if (test === 'external-auth-custom-oidc') {
|
||||||
result = await diagExternalAuthCustomOIDC(test, options)
|
result = await diagExternalAuthOIDC(test, options, 'custom', 'external-auth-google-oidc')
|
||||||
|
} else if (test === 'external-auth-google-oidc') {
|
||||||
|
result = await diagExternalAuthOIDC(test, options, 'google', 'external-auth-facebook-oidc')
|
||||||
|
} else if (test === 'external-auth-facebook-oidc') {
|
||||||
|
result = await diagExternalAuthOIDC(test, options, 'facebook', 'everything-ok')
|
||||||
} else if (test === 'everything-ok') {
|
} else if (test === 'everything-ok') {
|
||||||
result = newResult(test)
|
result = newResult(test)
|
||||||
result.label = 'Everything seems fine'
|
result.label = 'Everything seems fine'
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
type nextValue = 'backend' | 'debug' | 'webchat-video' | 'prosody' | 'external-auth-custom-oidc' | 'everything-ok'
|
type NextValue = 'backend' | 'debug' | 'webchat-video' | 'prosody'
|
||||||
|
| 'external-auth-custom-oidc' | 'external-auth-google-oidc' | 'external-auth-facebook-oidc'
|
||||||
|
| 'everything-ok'
|
||||||
|
|
||||||
interface MessageWithLevel {
|
interface MessageWithLevel {
|
||||||
level: 'info' | 'warning' | 'error'
|
level: 'info' | 'warning' | 'error'
|
||||||
@ -15,7 +17,7 @@ export interface TestResult {
|
|||||||
title: string
|
title: string
|
||||||
message: string
|
message: string
|
||||||
}>
|
}>
|
||||||
next: nextValue | null
|
next: NextValue | null
|
||||||
ok: boolean
|
ok: boolean
|
||||||
test: string
|
test: string
|
||||||
}
|
}
|
||||||
|
@ -49,13 +49,14 @@ function getMimeTypeFromArrayBuffer (arrayBuffer: ArrayBuffer): AcceptableAvatar
|
|||||||
type UserInfoField = 'username' | 'last_name' | 'first_name' | 'nickname' | 'picture'
|
type UserInfoField = 'username' | 'last_name' | 'first_name' | 'nickname' | 'picture'
|
||||||
|
|
||||||
interface UnserializedToken {
|
interface UnserializedToken {
|
||||||
|
type: ExternalAuthOIDCType
|
||||||
jid: string
|
jid: string
|
||||||
password: string
|
password: string
|
||||||
nickname: string
|
nickname: string
|
||||||
expire: Date
|
expire: Date
|
||||||
}
|
}
|
||||||
|
|
||||||
let singleton: ExternalAuthOIDC | undefined
|
let singletons: Map<ExternalAuthOIDCType, ExternalAuthOIDC> | undefined
|
||||||
|
|
||||||
async function getRandomBytes (size: number): Promise<Buffer> {
|
async function getRandomBytes (size: number): Promise<Buffer> {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@ -67,10 +68,13 @@ async function getRandomBytes (size: number): Promise<Buffer> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ExternalAuthOIDCType = 'custom' | 'google' | 'facebook'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This class handles the external OpenId Connect provider, if defined.
|
* This class handles the external OpenId Connect provider, if defined.
|
||||||
*/
|
*/
|
||||||
class ExternalAuthOIDC {
|
class ExternalAuthOIDC {
|
||||||
|
private readonly singletonType: ExternalAuthOIDCType
|
||||||
private readonly enabled: boolean
|
private readonly enabled: boolean
|
||||||
private readonly buttonLabel: string | undefined
|
private readonly buttonLabel: string | undefined
|
||||||
private readonly discoveryUrl: string | undefined
|
private readonly discoveryUrl: string | undefined
|
||||||
@ -82,7 +86,6 @@ class ExternalAuthOIDC {
|
|||||||
private readonly externalVirtualhost: string
|
private readonly externalVirtualhost: string
|
||||||
private readonly avatarsDir: string
|
private readonly avatarsDir: string
|
||||||
private readonly avatarsFiles: string[]
|
private readonly avatarsFiles: string[]
|
||||||
private pruneTimer?: NodeJS.Timer
|
|
||||||
|
|
||||||
private readonly encryptionOptions = {
|
private readonly encryptionOptions = {
|
||||||
algorithm: 'aes256' as string,
|
algorithm: 'aes256' as string,
|
||||||
@ -113,6 +116,7 @@ class ExternalAuthOIDC {
|
|||||||
|
|
||||||
constructor (params: {
|
constructor (params: {
|
||||||
logger: RegisterServerOptions['peertubeHelpers']['logger']
|
logger: RegisterServerOptions['peertubeHelpers']['logger']
|
||||||
|
singletonType: ExternalAuthOIDCType
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
buttonLabel: string | undefined
|
buttonLabel: string | undefined
|
||||||
discoveryUrl: string | undefined
|
discoveryUrl: string | undefined
|
||||||
@ -132,6 +136,7 @@ class ExternalAuthOIDC {
|
|||||||
error: (s) => params.logger.error('[ExternalAuthOIDC] ' + s)
|
error: (s) => params.logger.error('[ExternalAuthOIDC] ' + s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.singletonType = params.singletonType
|
||||||
this.enabled = !!params.enabled
|
this.enabled = !!params.enabled
|
||||||
this.secretKey = params.secretKey
|
this.secretKey = params.secretKey
|
||||||
this.redirectUrl = params.redirectUrl
|
this.redirectUrl = params.redirectUrl
|
||||||
@ -148,6 +153,13 @@ class ExternalAuthOIDC {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This singleton type.
|
||||||
|
*/
|
||||||
|
public get type (): ExternalAuthOIDCType {
|
||||||
|
return this.singletonType
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates that the OIDC is disabled.
|
* Indicates that the OIDC is disabled.
|
||||||
* Caution: this does not indicate if it is enabled, but poorly configured.
|
* Caution: this does not indicate if it is enabled, but poorly configured.
|
||||||
@ -390,13 +402,15 @@ class ExternalAuthOIDC {
|
|||||||
// Now we will encrypt jid + password, and return it to the browser.
|
// Now we will encrypt jid + password, and return it to the browser.
|
||||||
// The browser will be able to use this encrypted data with the api/configuration/room API.
|
// The browser will be able to use this encrypted data with the api/configuration/room API.
|
||||||
const tokenContent: UnserializedToken = {
|
const tokenContent: UnserializedToken = {
|
||||||
|
type: this.type,
|
||||||
jid,
|
jid,
|
||||||
password,
|
password,
|
||||||
nickname,
|
nickname,
|
||||||
// expires in 12 hours (user will just have to do the whole process again).
|
// expires in 12 hours (user will just have to do the whole process again).
|
||||||
expire: (new Date(Date.now() + 12 * 3600 * 1000))
|
expire: (new Date(Date.now() + 12 * 3600 * 1000))
|
||||||
}
|
}
|
||||||
const token = await this.encrypt(JSON.stringify(tokenContent))
|
// Token is prefixed by the type, so we can get the correct singleton for deserializing.
|
||||||
|
const token = this.type + '-' + await this.encrypt(JSON.stringify(tokenContent))
|
||||||
|
|
||||||
let avatar = await this.readUserInfoPicture(userInfo)
|
let avatar = await this.readUserInfoPicture(userInfo)
|
||||||
if (!avatar) {
|
if (!avatar) {
|
||||||
@ -447,12 +461,24 @@ class ExternalAuthOIDC {
|
|||||||
*/
|
*/
|
||||||
public async unserializeToken (token: string): Promise<UnserializedToken | null> {
|
public async unserializeToken (token: string): Promise<UnserializedToken | null> {
|
||||||
try {
|
try {
|
||||||
|
// First, check the prefix:
|
||||||
|
if (!token.startsWith(this.type + '-')) {
|
||||||
|
throw new Error('Wrong token prefix')
|
||||||
|
}
|
||||||
|
// Removing the prefix:
|
||||||
|
token = token.substring(this.type.length + 1)
|
||||||
|
|
||||||
const decrypted = await this.decrypt(token)
|
const decrypted = await this.decrypt(token)
|
||||||
const o = JSON.parse(decrypted) // can fail
|
const o = JSON.parse(decrypted) // can fail
|
||||||
|
|
||||||
if (typeof o !== 'object') {
|
if (typeof o !== 'object') {
|
||||||
throw new Error('Invalid encrypted data')
|
throw new Error('Invalid encrypted data')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (o.type !== this.type) {
|
||||||
|
throw new Error('Token type is not the expected one')
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof o.jid !== 'string' || o.jid === '') {
|
if (typeof o.jid !== 'string' || o.jid === '') {
|
||||||
throw new Error('No jid')
|
throw new Error('No jid')
|
||||||
}
|
}
|
||||||
@ -473,6 +499,7 @@ class ExternalAuthOIDC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
type: o.type,
|
||||||
jid: o.jid,
|
jid: o.jid,
|
||||||
password: o.password,
|
password: o.password,
|
||||||
nickname: o.nickname,
|
nickname: o.nickname,
|
||||||
@ -623,107 +650,146 @@ class ExternalAuthOIDC {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Starts an interval timer to prune external users from Prosody.
|
* frees the singletons
|
||||||
* @param options Peertube server options.
|
|
||||||
*/
|
*/
|
||||||
public startPruneTimer (options: RegisterServerOptions): void {
|
public static async destroySingletons (): Promise<void> {
|
||||||
this.stopPruneTimer() // just in case...
|
if (!singletons) { return }
|
||||||
|
|
||||||
// every hour (every minutes in debug mode)
|
stopPruneTimer()
|
||||||
const pruneInterval = debugNumericParameter(options, 'externalAccountPruneInterval', 60 * 1000, 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
|
const keys = singletons.keys()
|
||||||
this.pruneTimer = setInterval(async () => {
|
for (const key of keys) {
|
||||||
try {
|
const singleton = singletons.get(key)
|
||||||
if (!await this.isOk()) { return }
|
if (!singleton) { continue }
|
||||||
|
singletons.delete(key)
|
||||||
|
}
|
||||||
|
|
||||||
this.logger.info('Pruning external users...')
|
singletons = undefined
|
||||||
await pruneUsers(options)
|
|
||||||
} catch (err) {
|
|
||||||
this.logger.error('Error while pruning external users: ' + (err as string))
|
|
||||||
}
|
|
||||||
}, pruneInterval)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops the prune timer.
|
* Instanciate all singletons.
|
||||||
|
* Note: no need to destroy singletons before creating new ones.
|
||||||
*/
|
*/
|
||||||
public stopPruneTimer (): void {
|
public static async initSingletons (options: RegisterServerOptions): Promise<void> {
|
||||||
if (!this.pruneTimer) { return }
|
const prosodyDomain = await getProsodyDomain(options)
|
||||||
clearInterval(this.pruneTimer)
|
// FIXME: this is not optimal to call here.
|
||||||
this.pruneTimer = undefined
|
const prosodyFilePaths = await getProsodyFilePaths(options)
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* frees the singleton
|
|
||||||
*/
|
|
||||||
public static async destroySingleton (): Promise<void> {
|
|
||||||
if (!singleton) { return }
|
|
||||||
singleton.stopPruneTimer()
|
|
||||||
singleton = undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Instanciate the singleton.
|
|
||||||
* Note: no need to destroy the singleton before creating a new one.
|
|
||||||
*/
|
|
||||||
public static async initSingleton (options: RegisterServerOptions): Promise<ExternalAuthOIDC> {
|
|
||||||
const settings = await options.settingsManager.getSettings([
|
const settings = await options.settingsManager.getSettings([
|
||||||
'external-auth-custom-oidc',
|
'external-auth-custom-oidc',
|
||||||
'external-auth-custom-oidc-button-label',
|
'external-auth-custom-oidc-button-label',
|
||||||
'external-auth-custom-oidc-discovery-url',
|
'external-auth-custom-oidc-discovery-url',
|
||||||
'external-auth-custom-oidc-client-id',
|
'external-auth-custom-oidc-client-id',
|
||||||
'external-auth-custom-oidc-client-secret'
|
'external-auth-custom-oidc-client-secret',
|
||||||
|
'external-auth-google-oidc',
|
||||||
|
'external-auth-google-oidc-client-id',
|
||||||
|
'external-auth-google-oidc-client-secret',
|
||||||
|
'external-auth-facebook-oidc',
|
||||||
|
'external-auth-facebook-oidc-client-id',
|
||||||
|
'external-auth-facebook-oidc-client-secret'
|
||||||
])
|
])
|
||||||
|
|
||||||
// Generating a secret key that will be used for the authenticatio process (can change on restart).
|
const init = async function initSingleton (
|
||||||
const secretKey = (await getRandomBytes(16)).toString('hex')
|
singletonType: ExternalAuthOIDCType,
|
||||||
|
buttonLabel: string | undefined,
|
||||||
|
discoveryUrl: string | undefined
|
||||||
|
): Promise<void> {
|
||||||
|
// Generating a secret key that will be used for the authenticatio process (can change on restart).
|
||||||
|
const secretKey = (await getRandomBytes(16)).toString('hex')
|
||||||
|
|
||||||
const prosodyDomain = await getProsodyDomain(options)
|
const singleton = new ExternalAuthOIDC({
|
||||||
|
logger: options.peertubeHelpers.logger,
|
||||||
|
singletonType,
|
||||||
|
enabled: settings['external-auth-' + singletonType + '-oidc'] as boolean,
|
||||||
|
buttonLabel,
|
||||||
|
discoveryUrl,
|
||||||
|
clientId: settings['external-auth-' + singletonType + '-oidc-client-id'] as string | undefined,
|
||||||
|
clientSecret: settings['external-auth-' + singletonType + '-oidc-client-secret'] as string | undefined,
|
||||||
|
secretKey,
|
||||||
|
connectUrl: ExternalAuthOIDC.connectUrl(options, singletonType),
|
||||||
|
redirectUrl: ExternalAuthOIDC.redirectUrl(options, singletonType),
|
||||||
|
externalVirtualhost: 'external.' + prosodyDomain,
|
||||||
|
avatarsDir: prosodyFilePaths.avatars,
|
||||||
|
avatarsFiles: prosodyFilePaths.avatarsFiles
|
||||||
|
})
|
||||||
|
|
||||||
// FIXME: this is not optimal to call here.
|
singletons ??= new Map<ExternalAuthOIDCType, ExternalAuthOIDC>()
|
||||||
const prosodyFilePaths = await getProsodyFilePaths(options)
|
singletons.set(singletonType, singleton)
|
||||||
|
}
|
||||||
|
|
||||||
singleton = new ExternalAuthOIDC({
|
await Promise.all([
|
||||||
logger: options.peertubeHelpers.logger,
|
init(
|
||||||
enabled: settings['external-auth-custom-oidc'] as boolean,
|
'custom',
|
||||||
buttonLabel: settings['external-auth-custom-oidc-button-label'] as string | undefined,
|
settings['external-auth-custom-oidc-button-label'] as string | undefined,
|
||||||
discoveryUrl: settings['external-auth-custom-oidc-discovery-url'] as string | undefined,
|
settings['external-auth-custom-oidc-discovery-url'] as string | undefined
|
||||||
clientId: settings['external-auth-custom-oidc-client-id'] as string | undefined,
|
),
|
||||||
clientSecret: settings['external-auth-custom-oidc-client-secret'] as string | undefined,
|
init(
|
||||||
secretKey,
|
'google',
|
||||||
connectUrl: ExternalAuthOIDC.connectUrl(options),
|
'Google',
|
||||||
redirectUrl: ExternalAuthOIDC.redirectUrl(options),
|
'https://accounts.google.com'
|
||||||
externalVirtualhost: 'external.' + prosodyDomain,
|
),
|
||||||
avatarsDir: prosodyFilePaths.avatars,
|
init(
|
||||||
avatarsFiles: prosodyFilePaths.avatarsFiles
|
'facebook',
|
||||||
})
|
'Facebook',
|
||||||
|
'https://www.facebook.com'
|
||||||
|
)
|
||||||
|
])
|
||||||
|
|
||||||
singleton.startPruneTimer(options)
|
startPruneTimer(options)
|
||||||
|
|
||||||
return singleton
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the singleton, or raise an exception if it is too soon.
|
* Gets the singleton, or raise an exception if it is too soon.
|
||||||
|
* @param ExternalAuthOIDCType The singleton type.
|
||||||
* @throws Error
|
* @throws Error
|
||||||
* @returns the singleton
|
* @returns the singleton
|
||||||
*/
|
*/
|
||||||
public static singleton (): ExternalAuthOIDC {
|
public static singleton (singletonType: ExternalAuthOIDCType | string): ExternalAuthOIDC {
|
||||||
|
if (!singletons) {
|
||||||
|
throw new Error('ExternalAuthOIDC singletons are not initialized yet')
|
||||||
|
}
|
||||||
|
const singleton = singletons.get(singletonType as ExternalAuthOIDCType)
|
||||||
if (!singleton) {
|
if (!singleton) {
|
||||||
throw new Error('ExternalAuthOIDC singleton is not initialized yet')
|
throw new Error(`ExternalAuthOIDC singleton "${singletonType}" is not initiliazed yet`)
|
||||||
}
|
}
|
||||||
return singleton
|
return singleton
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all initialiazed singletons.
|
||||||
|
*/
|
||||||
|
public static allSingletons (): ExternalAuthOIDC[] {
|
||||||
|
if (!singletons) { return [] }
|
||||||
|
return Array.from(singletons.values())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reading header X-Peertube-Plugin-Livechat-External-Auth-OIDC-Token,
|
||||||
|
* got the singleton that is supposed to read the token.
|
||||||
|
* Note: the token must be unserialized before supposing it is valid!
|
||||||
|
* @param token the authentication token
|
||||||
|
*/
|
||||||
|
public static singletonForToken (token: string): ExternalAuthOIDC | null {
|
||||||
|
try {
|
||||||
|
const m = token.match(/^(\w+)-/)
|
||||||
|
if (!m) { return null }
|
||||||
|
return ExternalAuthOIDC.singleton(m[1])
|
||||||
|
} catch (err) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the uri to start the authentication process.
|
* Get the uri to start the authentication process.
|
||||||
* @param options Peertube server options
|
* @param options Peertube server options
|
||||||
* @returns the uri
|
* @returns the uri
|
||||||
*/
|
*/
|
||||||
public static connectUrl (options: RegisterServerOptions): string {
|
public static connectUrl (options: RegisterServerOptions, type: ExternalAuthOIDCType): string {
|
||||||
const path = getBaseRouterRoute(options) + 'oidc/connect'
|
if (!/^\w+$/.test(type)) {
|
||||||
|
throw new Error('Invalid singleton type')
|
||||||
|
}
|
||||||
|
const path = getBaseRouterRoute(options) + 'oidc/' + type + '/connect'
|
||||||
return canonicalizePluginUri(options, path, {
|
return canonicalizePluginUri(options, path, {
|
||||||
removePluginVersion: true
|
removePluginVersion: true
|
||||||
})
|
})
|
||||||
@ -734,14 +800,67 @@ class ExternalAuthOIDC {
|
|||||||
* @param options Peertube server optiosn
|
* @param options Peertube server optiosn
|
||||||
* @returns the uri
|
* @returns the uri
|
||||||
*/
|
*/
|
||||||
public static redirectUrl (options: RegisterServerOptions): string {
|
public static redirectUrl (options: RegisterServerOptions, type: ExternalAuthOIDCType): string {
|
||||||
const path = getBaseRouterRoute(options) + 'oidc/cb'
|
if (!/^\w+$/.test(type)) {
|
||||||
|
throw new Error('Invalid singleton type')
|
||||||
|
}
|
||||||
|
const path = getBaseRouterRoute(options) + 'oidc/' + type + '/cb'
|
||||||
return canonicalizePluginUri(options, path, {
|
return canonicalizePluginUri(options, path, {
|
||||||
removePluginVersion: true
|
removePluginVersion: true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
let pruneTimer: NodeJS.Timer | undefined
|
||||||
ExternalAuthOIDC
|
|
||||||
|
/**
|
||||||
|
* Starts an interval timer to prune external users from Prosody.
|
||||||
|
* @param options Peertube server options.
|
||||||
|
*/
|
||||||
|
function startPruneTimer (options: RegisterServerOptions): void {
|
||||||
|
stopPruneTimer() // just in case...
|
||||||
|
|
||||||
|
const logger = {
|
||||||
|
debug: (s: string) => options.peertubeHelpers.logger.debug('[ExternalAuthOIDC startPruneTimer] ' + s),
|
||||||
|
info: (s: string) => options.peertubeHelpers.logger.info('[ExternalAuthOIDC startPruneTimer] ' + s),
|
||||||
|
warn: (s: string) => options.peertubeHelpers.logger.warn('[ExternalAuthOIDC startPruneTimer] ' + s),
|
||||||
|
error: (s: string) => options.peertubeHelpers.logger.error('[ExternalAuthOIDC startPruneTimer] ' + s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// every hour (every minutes in debug mode)
|
||||||
|
const pruneInterval = debugNumericParameter(options, 'externalAccountPruneInterval', 60 * 1000, 60 * 60 * 1000)
|
||||||
|
logger.info(`Creating a timer for external account pruning, every ${Math.round(pruneInterval / 1000)}s.`)
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
pruneTimer = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
// Checking if at least one active singleton
|
||||||
|
let ok = false
|
||||||
|
for (const oidc of ExternalAuthOIDC.allSingletons()) {
|
||||||
|
if (!await oidc.isOk()) { continue }
|
||||||
|
ok = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if (!ok) { return }
|
||||||
|
|
||||||
|
logger.info('Pruning external users...')
|
||||||
|
await pruneUsers(options)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error('Error while pruning external users: ' + (err as string))
|
||||||
|
}
|
||||||
|
}, pruneInterval)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stops the prune timer.
|
||||||
|
*/
|
||||||
|
function stopPruneTimer (): void {
|
||||||
|
if (!pruneTimer) { return }
|
||||||
|
clearInterval(pruneTimer)
|
||||||
|
pruneTimer = undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ExternalAuthOIDC,
|
||||||
|
ExternalAuthOIDCType
|
||||||
}
|
}
|
||||||
|
@ -197,9 +197,13 @@ async function getProsodyConfig (options: RegisterServerOptionsV5): Promise<Pros
|
|||||||
|
|
||||||
let useExternal: boolean = false
|
let useExternal: boolean = false
|
||||||
try {
|
try {
|
||||||
const oidc = ExternalAuthOIDC.singleton()
|
const oidcs = ExternalAuthOIDC.allSingletons()
|
||||||
if (await oidc.isOk()) {
|
for (const oidc of oidcs) {
|
||||||
useExternal = true
|
if (await oidc.isOk()) {
|
||||||
|
// At least one external authentcation => we must enable the external virtual host.
|
||||||
|
useExternal = true
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(err)
|
logger.error(err)
|
||||||
|
@ -21,8 +21,8 @@ async function initAuthApiRouter (options: RegisterServerOptions, router: Router
|
|||||||
const token = req.header('X-Peertube-Plugin-Livechat-External-Auth-OIDC-Token')
|
const token = req.header('X-Peertube-Plugin-Livechat-External-Auth-OIDC-Token')
|
||||||
if (token) {
|
if (token) {
|
||||||
try {
|
try {
|
||||||
const oidc = ExternalAuthOIDC.singleton()
|
const oidc = ExternalAuthOIDC.singletonForToken(token)
|
||||||
if (await oidc.isOk()) {
|
if (oidc && await oidc.isOk()) {
|
||||||
const unserializedToken = await oidc.unserializeToken(token)
|
const unserializedToken = await oidc.unserializeToken(token)
|
||||||
if (unserializedToken) {
|
if (unserializedToken) {
|
||||||
res.status(200).json({
|
res.status(200).json({
|
||||||
|
@ -36,11 +36,12 @@ async function initOIDCRouter (options: RegisterServerOptions): Promise<Router>
|
|||||||
const router = getRouter()
|
const router = getRouter()
|
||||||
const logger = peertubeHelpers.logger
|
const logger = peertubeHelpers.logger
|
||||||
|
|
||||||
router.get('/connect', asyncMiddleware(
|
router.get('/:type?/connect', asyncMiddleware(
|
||||||
async (req: Request, res: Response, next: NextFunction) => {
|
async (req: Request, res: Response, next: NextFunction) => {
|
||||||
logger.info('[oidc router] OIDC connect call')
|
const singletonType = req.params.type ?? 'custom'
|
||||||
|
logger.info('[oidc router] OIDC connect call (' + singletonType + ')')
|
||||||
try {
|
try {
|
||||||
const oidc = ExternalAuthOIDC.singleton()
|
const oidc = ExternalAuthOIDC.singleton(singletonType)
|
||||||
const oidcClient = await oidc.load()
|
const oidcClient = await oidc.load()
|
||||||
if (!oidcClient) {
|
if (!oidcClient) {
|
||||||
throw new Error('[oidc router] External Auth OIDC not loaded yet')
|
throw new Error('[oidc router] External Auth OIDC not loaded yet')
|
||||||
@ -57,9 +58,10 @@ async function initOIDCRouter (options: RegisterServerOptions): Promise<Router>
|
|||||||
|
|
||||||
const cbHandler = asyncMiddleware(
|
const cbHandler = asyncMiddleware(
|
||||||
async (req: Request, res: Response, _next: NextFunction) => {
|
async (req: Request, res: Response, _next: NextFunction) => {
|
||||||
logger.info('[oidc router] OIDC callback call')
|
const singletonType = req.params.type ?? 'custom'
|
||||||
|
logger.info('[oidc router] OIDC callback call (' + singletonType + ')')
|
||||||
try {
|
try {
|
||||||
const oidc = ExternalAuthOIDC.singleton()
|
const oidc = ExternalAuthOIDC.singleton(singletonType)
|
||||||
const oidcClient = await oidc.load()
|
const oidcClient = await oidc.load()
|
||||||
if (!oidcClient) {
|
if (!oidcClient) {
|
||||||
throw new Error('[oidc router] External Auth OIDC not loaded yet')
|
throw new Error('[oidc router] External Auth OIDC not loaded yet')
|
||||||
@ -102,8 +104,8 @@ async function initOIDCRouter (options: RegisterServerOptions): Promise<Router>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
router.get('/cb', cbHandler)
|
router.get('/:type?/cb', cbHandler)
|
||||||
router.post('/cb', cbHandler)
|
router.post('/:type?/cb', cbHandler)
|
||||||
|
|
||||||
return router
|
return router
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import type { ConverseJSTheme } from '../../shared/lib/types'
|
|||||||
import { ensureProsodyRunning } from './prosody/ctl'
|
import { ensureProsodyRunning } from './prosody/ctl'
|
||||||
import { RoomChannel } from './room-channel'
|
import { RoomChannel } from './room-channel'
|
||||||
import { BotsCtl } from './bots/ctl'
|
import { BotsCtl } from './bots/ctl'
|
||||||
import { ExternalAuthOIDC } from './external-auth/oidc'
|
import { ExternalAuthOIDC, ExternalAuthOIDCType } from './external-auth/oidc'
|
||||||
import { loc } from './loc'
|
import { loc } from './loc'
|
||||||
const escapeHTML = require('escape-html')
|
const escapeHTML = require('escape-html')
|
||||||
|
|
||||||
@ -22,31 +22,35 @@ async function initSettings (options: RegisterServerOptions): Promise<void> {
|
|||||||
initThemingSettings(options)
|
initThemingSettings(options)
|
||||||
initChatServerAdvancedSettings(options)
|
initChatServerAdvancedSettings(options)
|
||||||
|
|
||||||
await ExternalAuthOIDC.initSingleton(options)
|
await ExternalAuthOIDC.initSingletons(options)
|
||||||
const loadOidc = (): void => {
|
const loadOidcs = (): void => {
|
||||||
try {
|
const oidcs = ExternalAuthOIDC.allSingletons()
|
||||||
const oidc = ExternalAuthOIDC.singleton()
|
for (const oidc of oidcs) {
|
||||||
oidc.isOk().then(
|
try {
|
||||||
() => {
|
const type = oidc.type
|
||||||
logger.info('Loading External Auth OIDC...')
|
oidc.isOk().then(
|
||||||
oidc.load().then(
|
() => {
|
||||||
() => {
|
logger.info(`Loading External Auth OIDC ${type}...`)
|
||||||
logger.info('External Auth OIDC loaded')
|
oidc.load().then(
|
||||||
},
|
() => {
|
||||||
() => {
|
logger.info(`External Auth OIDC ${type} loaded`)
|
||||||
logger.error('Loading the External Auth OIDC failed')
|
},
|
||||||
}
|
() => {
|
||||||
)
|
logger.error(`Loading the External Auth OIDC ${type} failed`)
|
||||||
},
|
}
|
||||||
() => {
|
)
|
||||||
logger.info('No valid External Auth OIDC, nothing loaded')
|
},
|
||||||
}
|
() => {
|
||||||
)
|
logger.info(`No valid External Auth OIDC ${type}, nothing loaded`)
|
||||||
} catch (err) {
|
}
|
||||||
logger.error(err as string)
|
)
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(err as string)
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
loadOidc() // we don't have to wait (can take time, it will do external http requests)
|
loadOidcs() // we don't have to wait (can take time, it will do external http requests)
|
||||||
|
|
||||||
let currentProsodyRoomtype = (await settingsManager.getSettings(['prosody-room-type']))['prosody-room-type']
|
let currentProsodyRoomtype = (await settingsManager.getSettings(['prosody-room-type']))['prosody-room-type']
|
||||||
|
|
||||||
@ -56,9 +60,9 @@ async function initSettings (options: RegisterServerOptions): Promise<void> {
|
|||||||
// To avoid race condition, we will just stop and start the bots at every settings saving.
|
// To avoid race condition, we will just stop and start the bots at every settings saving.
|
||||||
await BotsCtl.destroySingleton()
|
await BotsCtl.destroySingleton()
|
||||||
await BotsCtl.initSingleton(options)
|
await BotsCtl.initSingleton(options)
|
||||||
loadOidc() // we don't have to wait (can take time, it will do external http requests)
|
loadOidcs() // we don't have to wait (can take time, it will do external http requests)
|
||||||
|
|
||||||
await ExternalAuthOIDC.initSingleton(options)
|
await ExternalAuthOIDC.initSingletons(options)
|
||||||
|
|
||||||
peertubeHelpers.logger.info('Saving settings, ensuring prosody is running')
|
peertubeHelpers.logger.info('Saving settings, ensuring prosody is running')
|
||||||
await ensureProsodyRunning(options)
|
await ensureProsodyRunning(options)
|
||||||
@ -206,13 +210,13 @@ function initExternalAuth (options: RegisterServerOptions): void {
|
|||||||
type: 'html',
|
type: 'html',
|
||||||
name: 'external-auth-custom-oidc-redirect-uris-info',
|
name: 'external-auth-custom-oidc-redirect-uris-info',
|
||||||
private: true,
|
private: true,
|
||||||
descriptionHTML: loc('external_auth_custom_oidc_redirect_uris_info_description')
|
descriptionHTML: loc('external_auth_oidc_redirect_uris_info_description')
|
||||||
})
|
})
|
||||||
registerSetting({
|
registerSetting({
|
||||||
type: 'html',
|
type: 'html',
|
||||||
name: 'external-auth-custom-oidc-redirect-uris',
|
name: 'external-auth-custom-oidc-redirect-uris',
|
||||||
private: true,
|
private: true,
|
||||||
descriptionHTML: `<ul><li>${escapeHTML(ExternalAuthOIDC.redirectUrl(options)) as string}</li></ul>`
|
descriptionHTML: `<ul><li>${escapeHTML(ExternalAuthOIDC.redirectUrl(options, 'custom')) as string}</li></ul>`
|
||||||
})
|
})
|
||||||
|
|
||||||
registerSetting({
|
registerSetting({
|
||||||
@ -232,15 +236,15 @@ function initExternalAuth (options: RegisterServerOptions): void {
|
|||||||
})
|
})
|
||||||
registerSetting({
|
registerSetting({
|
||||||
name: 'external-auth-custom-oidc-client-id',
|
name: 'external-auth-custom-oidc-client-id',
|
||||||
label: loc('external_auth_custom_oidc_client_id_label'),
|
label: loc('external_auth_oidc_client_id_label'),
|
||||||
// descriptionHTML: loc('external_auth_custom_oidc_client_id_description'),
|
// descriptionHTML: loc('external_auth_oidc_client_id_description'),
|
||||||
type: 'input',
|
type: 'input',
|
||||||
private: true
|
private: true
|
||||||
})
|
})
|
||||||
registerSetting({
|
registerSetting({
|
||||||
name: 'external-auth-custom-oidc-client-secret',
|
name: 'external-auth-custom-oidc-client-secret',
|
||||||
label: loc('external_auth_custom_oidc_client_secret_label'),
|
label: loc('external_auth_oidc_client_secret_label'),
|
||||||
// descriptionHTML: loc('external_auth_custom_oidc_client_secret_description'),
|
// descriptionHTML: loc('external_auth_oidc_client_secret_description'),
|
||||||
type: 'input-password',
|
type: 'input-password',
|
||||||
private: true
|
private: true
|
||||||
})
|
})
|
||||||
@ -250,6 +254,52 @@ function initExternalAuth (options: RegisterServerOptions): void {
|
|||||||
// - user-name property
|
// - user-name property
|
||||||
// - display-name property
|
// - display-name property
|
||||||
// - picture property
|
// - picture property
|
||||||
|
|
||||||
|
// Standard providers....
|
||||||
|
for (const provider of ['google', 'facebook']) {
|
||||||
|
let redirectUrl
|
||||||
|
try {
|
||||||
|
redirectUrl = ExternalAuthOIDC.redirectUrl(options, provider as ExternalAuthOIDCType)
|
||||||
|
} catch (err) {
|
||||||
|
options.peertubeHelpers.logger.error('Cant load redirect url for provider ' + provider)
|
||||||
|
options.peertubeHelpers.logger.error(err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
registerSetting({
|
||||||
|
name: 'external-auth-' + provider + '-oidc',
|
||||||
|
label: loc('external_auth_' + provider + '_oidc_label'),
|
||||||
|
descriptionHTML: loc('external_auth_' + provider + '_oidc_description'),
|
||||||
|
type: 'input-checkbox',
|
||||||
|
default: false,
|
||||||
|
private: true
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
type: 'html',
|
||||||
|
name: 'external-auth-' + provider + '-oidc-redirect-uris-info',
|
||||||
|
private: true,
|
||||||
|
descriptionHTML: loc('external_auth_oidc_redirect_uris_info_description')
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
type: 'html',
|
||||||
|
name: 'external-auth-' + provider + '-oidc-redirect-uris',
|
||||||
|
private: true,
|
||||||
|
descriptionHTML: `<ul><li>${escapeHTML(redirectUrl) as string}</li></ul>`
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
name: 'external-auth-' + provider + '-oidc-client-id',
|
||||||
|
label: loc('external_auth_oidc_client_id_label'),
|
||||||
|
// descriptionHTML: loc('external_auth_' + provider + '_oidc_client_id_description'),
|
||||||
|
type: 'input',
|
||||||
|
private: true
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
name: 'external-auth-' + provider + '-oidc-client-secret',
|
||||||
|
label: loc('external_auth_oidc_client_secret_label'),
|
||||||
|
// descriptionHTML: loc('external_auth_' + provider + '_oidc_client_secret_description'),
|
||||||
|
type: 'input-password',
|
||||||
|
private: true
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -94,7 +94,7 @@ async function unregister (): Promise<any> {
|
|||||||
|
|
||||||
await RoomChannel.destroySingleton()
|
await RoomChannel.destroySingleton()
|
||||||
await BotConfiguration.destroySingleton()
|
await BotConfiguration.destroySingleton()
|
||||||
await ExternalAuthOIDC.destroySingleton()
|
await ExternalAuthOIDC.destroySingletons()
|
||||||
|
|
||||||
const module = __filename
|
const module = __filename
|
||||||
OPTIONS?.peertubeHelpers.logger.info(`Unloading module ${module}...`)
|
OPTIONS?.peertubeHelpers.logger.info(`Unloading module ${module}...`)
|
||||||
|
@ -63,11 +63,11 @@ Just set here the discovery url, that should be something like `https://example.
|
|||||||
Note: if your provider use the standard `/.well-known/openid-configuration` path, you can omit it.
|
Note: if your provider use the standard `/.well-known/openid-configuration` path, you can omit it.
|
||||||
For example `https://accounts.google.com` will work.
|
For example `https://accounts.google.com` will work.
|
||||||
|
|
||||||
### {{% livechat_label external_auth_custom_oidc_client_id_label %}}
|
### {{% livechat_label external_auth_oidc_client_id_label %}}
|
||||||
|
|
||||||
Your application Client ID.
|
Your application Client ID.
|
||||||
|
|
||||||
### {{% livechat_label external_auth_custom_oidc_client_secret_label %}}
|
### {{% livechat_label external_auth_oidc_client_secret_label %}}
|
||||||
|
|
||||||
You application Client secret.
|
You application Client secret.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user