Possibility to configure an OpenID Connect provider on the instance level WIP (#128).
This commit is contained in:
parent
c1e877cb44
commit
514cc1d159
@ -7,15 +7,16 @@ TODO: https://github.com/JohnXLivingston/peertube-plugin-livechat/issues/48
|
|||||||
|
|
||||||
**Breaking changes**:
|
**Breaking changes**:
|
||||||
|
|
||||||
* if you were adding custom CSS to livechat iframe, it could be broken, as the livechat is no more included in an iframe. Your custom styles are now added on a `div` element.
|
* If you were adding custom CSS to livechat iframe, it could be broken, as the livechat is no more included in an iframe. Your custom styles are now added on a `div` element.
|
||||||
|
|
||||||
### New features
|
### New features
|
||||||
|
|
||||||
|
* For anonymous users: new "log in using an external account" dialog, with following options:
|
||||||
|
* remote Peertube account,
|
||||||
|
* #128: possibility to configure an OpenID Connect provider on the instance level.
|
||||||
* #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).
|
||||||
* For anonymous users: new "log in using an external account" dialog, with following options:
|
|
||||||
* remote Peertube account
|
|
||||||
* #355: ConverseJS dropdown menu available everywhere, inclusing when chat is besides the video.
|
* #355: ConverseJS dropdown menu available everywhere, inclusing when chat is besides the video.
|
||||||
|
|
||||||
### Minor changes and fixes
|
### Minor changes and fixes
|
||||||
|
@ -236,6 +236,10 @@ 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-')) {
|
||||||
|
return options.formValues['external-auth-custom-oidc'] !== true
|
||||||
|
}
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -67,6 +67,22 @@ federation_dont_publish_remotely_description: |
|
|||||||
<b>Please note</b>: this setting only affects the publication of information via the ActivityPub protocol.
|
<b>Please note</b>: this setting only affects the publication of information via the ActivityPub protocol.
|
||||||
It will not prevent a remote application from otherwise detecting the presence of chats, and trying to connect to it.
|
It will not prevent a remote application from otherwise detecting the presence of chats, and trying to connect to it.
|
||||||
|
|
||||||
|
external_auth_description: |
|
||||||
|
<h3>External authentication</h3>
|
||||||
|
For users that have no Peertube account, you can enable various authentication modes based on remote authentication providers.
|
||||||
|
|
||||||
|
external_auth_custom_oidc_label: "Use a custom OpenID Connect provider"
|
||||||
|
external_auth_custom_oidc_description: |
|
||||||
|
You can configure an external OpenID Connect provider that could be used to log in to the chat.
|
||||||
|
Please refer to the documentation:
|
||||||
|
<a href="https://johnxlivingston.github.io/peertube-plugin-livechat/documentation/admin/settings/" target="_blank">Settings</a>.
|
||||||
|
|
||||||
|
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_discovery_url_label: "Discovery URL"
|
||||||
|
external_auth_custom_oidc_client_id_label: "Client ID"
|
||||||
|
external_auth_custom_oidc_client_secret_label: "Client secret"
|
||||||
|
|
||||||
chat_behaviour_description: "<h3>Chat behaviour</h3>"
|
chat_behaviour_description: "<h3>Chat behaviour</h3>"
|
||||||
|
|
||||||
|
73
package-lock.json
generated
73
package-lock.json
generated
@ -15,6 +15,7 @@
|
|||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"log-rotate": "^0.2.8",
|
"log-rotate": "^0.2.8",
|
||||||
|
"openid-client": "^5.6.5",
|
||||||
"validate-color": "^2.2.1",
|
"validate-color": "^2.2.1",
|
||||||
"xmppjs-chat-bot": "^0.3.0"
|
"xmppjs-chat-bot": "^0.3.0"
|
||||||
},
|
},
|
||||||
@ -8493,6 +8494,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jose": {
|
||||||
|
"version": "4.15.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
|
||||||
|
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg==",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/js-tokens": {
|
"node_modules/js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -8708,7 +8717,6 @@
|
|||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
"dev": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
},
|
},
|
||||||
@ -9489,6 +9497,14 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/object-hash": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/object-inspect": {
|
"node_modules/object-inspect": {
|
||||||
"version": "1.12.3",
|
"version": "1.12.3",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||||
@ -9557,6 +9573,14 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/oidc-token-hash": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==",
|
||||||
|
"engines": {
|
||||||
|
"node": "^10.13.0 || >=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/on-finished": {
|
"node_modules/on-finished": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
@ -9601,6 +9625,20 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/openid-client": {
|
||||||
|
"version": "5.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz",
|
||||||
|
"integrity": "sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==",
|
||||||
|
"dependencies": {
|
||||||
|
"jose": "^4.15.5",
|
||||||
|
"lru-cache": "^6.0.0",
|
||||||
|
"object-hash": "^2.2.0",
|
||||||
|
"oidc-token-hash": "^5.0.3"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/panva"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/optionator": {
|
"node_modules/optionator": {
|
||||||
"version": "0.9.3",
|
"version": "0.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
||||||
@ -12299,8 +12337,7 @@
|
|||||||
"node_modules/yallist": {
|
"node_modules/yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"node_modules/yaml": {
|
"node_modules/yaml": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
@ -18721,6 +18758,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||||
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
|
"integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg=="
|
||||||
},
|
},
|
||||||
|
"jose": {
|
||||||
|
"version": "4.15.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/jose/-/jose-4.15.5.tgz",
|
||||||
|
"integrity": "sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg=="
|
||||||
|
},
|
||||||
"js-tokens": {
|
"js-tokens": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
|
||||||
@ -18909,7 +18951,6 @@
|
|||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||||
"dev": true,
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"yallist": "^4.0.0"
|
"yallist": "^4.0.0"
|
||||||
}
|
}
|
||||||
@ -19471,6 +19512,11 @@
|
|||||||
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"object-hash": {
|
||||||
|
"version": "2.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz",
|
||||||
|
"integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw=="
|
||||||
|
},
|
||||||
"object-inspect": {
|
"object-inspect": {
|
||||||
"version": "1.12.3",
|
"version": "1.12.3",
|
||||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz",
|
||||||
@ -19515,6 +19561,11 @@
|
|||||||
"es-abstract": "^1.19.1"
|
"es-abstract": "^1.19.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"oidc-token-hash": {
|
||||||
|
"version": "5.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz",
|
||||||
|
"integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw=="
|
||||||
|
},
|
||||||
"on-finished": {
|
"on-finished": {
|
||||||
"version": "2.4.1",
|
"version": "2.4.1",
|
||||||
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
|
||||||
@ -19550,6 +19601,17 @@
|
|||||||
"mimic-fn": "^2.1.0"
|
"mimic-fn": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"openid-client": {
|
||||||
|
"version": "5.6.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.6.5.tgz",
|
||||||
|
"integrity": "sha512-5P4qO9nGJzB5PI0LFlhj4Dzg3m4odt0qsJTfyEtZyOlkgpILwEioOhVVJOrS1iVH494S4Ee5OCjjg6Bf5WOj3w==",
|
||||||
|
"requires": {
|
||||||
|
"jose": "^4.15.5",
|
||||||
|
"lru-cache": "^6.0.0",
|
||||||
|
"object-hash": "^2.2.0",
|
||||||
|
"oidc-token-hash": "^5.0.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"optionator": {
|
"optionator": {
|
||||||
"version": "0.9.3",
|
"version": "0.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
|
||||||
@ -21519,8 +21581,7 @@
|
|||||||
"yallist": {
|
"yallist": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||||
"dev": true
|
|
||||||
},
|
},
|
||||||
"yaml": {
|
"yaml": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
|
@ -39,6 +39,7 @@
|
|||||||
"got": "^11.8.2",
|
"got": "^11.8.2",
|
||||||
"http-proxy": "^1.18.1",
|
"http-proxy": "^1.18.1",
|
||||||
"log-rotate": "^0.2.8",
|
"log-rotate": "^0.2.8",
|
||||||
|
"openid-client": "^5.6.5",
|
||||||
"validate-color": "^2.2.1",
|
"validate-color": "^2.2.1",
|
||||||
"xmppjs-chat-bot": "^0.3.0"
|
"xmppjs-chat-bot": "^0.3.0"
|
||||||
},
|
},
|
||||||
|
@ -10,6 +10,7 @@ import { getVideoLiveChatInfos } from '../federation/storage'
|
|||||||
import { getBaseRouterRoute, getBaseStaticRoute } from '../helpers'
|
import { getBaseRouterRoute, getBaseStaticRoute } from '../helpers'
|
||||||
import { getProsodyDomain } from '../prosody/config/domain'
|
import { getProsodyDomain } from '../prosody/config/domain'
|
||||||
import { getBoshUri, getWSUri } from '../uri/webchat'
|
import { getBoshUri, getWSUri } from '../uri/webchat'
|
||||||
|
import { ExternalAuthOIDC } from '../external-auth/oidc'
|
||||||
|
|
||||||
interface GetConverseJSParamsParams {
|
interface GetConverseJSParamsParams {
|
||||||
readonly?: boolean | 'noscroll'
|
readonly?: boolean | 'noscroll'
|
||||||
@ -76,6 +77,10 @@ async function getConverseJSParams (
|
|||||||
roomJID
|
roomJID
|
||||||
} = connectionInfos
|
} = connectionInfos
|
||||||
|
|
||||||
|
const oidc = ExternalAuthOIDC.singleton()
|
||||||
|
// TODO:
|
||||||
|
const externalAuthOIDC = await oidc.isOk() ? undefined : undefined
|
||||||
|
|
||||||
return {
|
return {
|
||||||
peertubeVideoOriginalUrl: roomInfos.video?.url,
|
peertubeVideoOriginalUrl: roomInfos.video?.url,
|
||||||
peertubeVideoUUID: roomInfos.video?.uuid,
|
peertubeVideoUUID: roomInfos.video?.uuid,
|
||||||
@ -98,7 +103,8 @@ async function getConverseJSParams (
|
|||||||
transparent,
|
transparent,
|
||||||
// forceDefaultHideMucParticipants is for testing purpose
|
// forceDefaultHideMucParticipants is for testing purpose
|
||||||
// (so we can stress test with the muc participant list hidden by default)
|
// (so we can stress test with the muc participant list hidden by default)
|
||||||
forceDefaultHideMucParticipants: params.forceDefaultHideMucParticipants
|
forceDefaultHideMucParticipants: params.forceDefaultHideMucParticipants,
|
||||||
|
externalAuthOIDC
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
server/lib/diagnostic/external-auth-custom-oidc.ts
Normal file
39
server/lib/diagnostic/external-auth-custom-oidc.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||||
|
import { newResult, TestResult } from './utils'
|
||||||
|
import { ExternalAuthOIDC } from '../external-auth/oidc'
|
||||||
|
|
||||||
|
export async function diagExternalAuthCustomOIDC (test: string, _options: RegisterServerOptions): Promise<TestResult> {
|
||||||
|
const result = newResult(test)
|
||||||
|
result.label = 'Test External Auth Custom OIDC'
|
||||||
|
result.next = 'everything-ok'
|
||||||
|
|
||||||
|
try {
|
||||||
|
const oidc = ExternalAuthOIDC.singleton()
|
||||||
|
|
||||||
|
if (oidc.isDisabledBySettings()) {
|
||||||
|
result.ok = true
|
||||||
|
result.messages.push('Feature disabled in plugins settings.')
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors = await oidc.check()
|
||||||
|
if (errors.length) {
|
||||||
|
result.messages.push({
|
||||||
|
level: 'error',
|
||||||
|
message: 'The ExternalAuthOIDC singleton got some errors:'
|
||||||
|
})
|
||||||
|
result.messages.push(...errors)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
result.messages.push({
|
||||||
|
level: 'error',
|
||||||
|
message: 'Error while retrieving the ExternalAuthOIDC singleton:' + (err as string)
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ok = true
|
||||||
|
result.messages.push('Configuration OK.')
|
||||||
|
return result
|
||||||
|
}
|
@ -4,6 +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 { 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> {
|
||||||
@ -17,6 +18,8 @@ export async function diag (test: string, options: RegisterServerOptions): Promi
|
|||||||
result = await diagVideo(test, options)
|
result = await diagVideo(test, options)
|
||||||
} 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') {
|
||||||
|
result = await diagExternalAuthCustomOIDC(test, options)
|
||||||
} 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'
|
||||||
|
@ -236,6 +236,6 @@ export async function diagProsody (test: string, options: RegisterServerOptions)
|
|||||||
}
|
}
|
||||||
|
|
||||||
result.ok = true
|
result.ok = true
|
||||||
result.next = 'everything-ok'
|
result.next = 'external-auth-custom-oidc'
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
type nextValue = 'backend' | 'debug' | 'webchat-video' | 'prosody' | 'everything-ok'
|
type nextValue = 'backend' | 'debug' | 'webchat-video' | 'prosody' | 'external-auth-custom-oidc' | 'everything-ok'
|
||||||
|
|
||||||
interface MessageWithLevel {
|
interface MessageWithLevel {
|
||||||
level: 'info' | 'warning' | 'error'
|
level: 'info' | 'warning' | 'error'
|
||||||
|
163
server/lib/external-auth/oidc.ts
Normal file
163
server/lib/external-auth/oidc.ts
Normal file
@ -0,0 +1,163 @@
|
|||||||
|
import type { RegisterServerOptions } from '@peertube/peertube-types'
|
||||||
|
import { URL } from 'url'
|
||||||
|
import { Issuer } from 'openid-client'
|
||||||
|
|
||||||
|
let singleton: ExternalAuthOIDC | undefined
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class handles the external OpenId Connect provider, if defined.
|
||||||
|
*/
|
||||||
|
class ExternalAuthOIDC {
|
||||||
|
private readonly enabled: boolean
|
||||||
|
private readonly buttonLabel: string | undefined
|
||||||
|
private readonly discoveryUrl: string | undefined
|
||||||
|
private readonly clientId: string | undefined
|
||||||
|
private readonly clientSecret: string | undefined
|
||||||
|
private ok: boolean | undefined
|
||||||
|
protected readonly logger: {
|
||||||
|
debug: (s: string) => void
|
||||||
|
info: (s: string) => void
|
||||||
|
warn: (s: string) => void
|
||||||
|
error: (s: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor (
|
||||||
|
logger: RegisterServerOptions['peertubeHelpers']['logger'],
|
||||||
|
enabled: boolean,
|
||||||
|
buttonLabel: string | undefined,
|
||||||
|
discoveryUrl: string | undefined,
|
||||||
|
clientId: string | undefined,
|
||||||
|
clientSecret: string | undefined
|
||||||
|
) {
|
||||||
|
this.logger = {
|
||||||
|
debug: (s) => logger.debug('[ExternalAuthOIDC] ' + s),
|
||||||
|
info: (s) => logger.info('[ExternalAuthOIDC] ' + s),
|
||||||
|
warn: (s) => logger.warn('[ExternalAuthOIDC] ' + s),
|
||||||
|
error: (s) => logger.error('[ExternalAuthOIDC] ' + s)
|
||||||
|
}
|
||||||
|
|
||||||
|
this.enabled = !!enabled
|
||||||
|
if (this.enabled) {
|
||||||
|
this.buttonLabel = buttonLabel
|
||||||
|
this.discoveryUrl = discoveryUrl
|
||||||
|
this.clientId = clientId
|
||||||
|
this.clientSecret = clientSecret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates that the OIDC is disabled.
|
||||||
|
* Caution: this does not indicate if it is enabled, but poorly configured.
|
||||||
|
* This method should only be used in the diagnostic tool.
|
||||||
|
*/
|
||||||
|
isDisabledBySettings (): boolean {
|
||||||
|
return !this.enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates if the OIDC provider is correctly configured.
|
||||||
|
* @param force If true, all checks will be forced again.
|
||||||
|
*/
|
||||||
|
async isOk (force?: boolean): Promise<boolean> {
|
||||||
|
// If we already checked it, just return the previous value.
|
||||||
|
if (!force && this.ok !== undefined) { return this.ok }
|
||||||
|
|
||||||
|
this.ok = (await this.check()).length === 0
|
||||||
|
return this.ok
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check the configuration.
|
||||||
|
* Returns an error list.
|
||||||
|
* If error list is empty, consider the OIDC is correctly configured.
|
||||||
|
*/
|
||||||
|
async check (): Promise<string[]> {
|
||||||
|
if (!this.enabled) {
|
||||||
|
this.logger.debug('OIDC is disabled')
|
||||||
|
return ['OIDC disabled']
|
||||||
|
}
|
||||||
|
|
||||||
|
const errors: string[] = []
|
||||||
|
if (this.buttonLabel === undefined) {
|
||||||
|
errors.push('Missing button label')
|
||||||
|
}
|
||||||
|
if (this.discoveryUrl === undefined) {
|
||||||
|
errors.push('Missing discovery url')
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
const uri = new URL(this.discoveryUrl)
|
||||||
|
this.logger.debug('OIDC Discovery url is valid: ' + uri.toString())
|
||||||
|
} catch (err) {
|
||||||
|
errors.push('Invalid discovery url')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (this.clientId === undefined) {
|
||||||
|
errors.push('Missing client id')
|
||||||
|
}
|
||||||
|
if (this.clientSecret === undefined) {
|
||||||
|
errors.push('Missing client secret')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length === 0) {
|
||||||
|
// Now we can try to use the discover service
|
||||||
|
try {
|
||||||
|
const issuer = await Issuer.discover(this.discoveryUrl as string)
|
||||||
|
this.logger.debug(`Discovered issuer, metadata are: ${JSON.stringify(issuer.metadata)}`)
|
||||||
|
} catch (err) {
|
||||||
|
this.logger.error(err as string)
|
||||||
|
errors.push(`Discovery URL non working: ${err as string}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
this.logger.error('OIDC is not ok: ' + JSON.stringify(errors))
|
||||||
|
}
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* frees the singleton
|
||||||
|
*/
|
||||||
|
public static async destroySingleton (): Promise<void> {
|
||||||
|
if (!singleton) { return }
|
||||||
|
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([
|
||||||
|
'external-auth-custom-oidc',
|
||||||
|
'external-auth-custom-oidc-button-label',
|
||||||
|
'external-auth-custom-oidc-discovery-url',
|
||||||
|
'external-auth-custom-oidc-client-id',
|
||||||
|
'external-auth-custom-oidc-client-secret'
|
||||||
|
])
|
||||||
|
singleton = new ExternalAuthOIDC(
|
||||||
|
options.peertubeHelpers.logger,
|
||||||
|
settings['external-auth-custom-oidc'] as boolean,
|
||||||
|
settings['external-auth-custom-oidc-button-label'] as string | undefined,
|
||||||
|
settings['external-auth-custom-oidc-discovery-url'] as string | undefined,
|
||||||
|
settings['external-auth-custom-oidc-client-id'] as string | undefined,
|
||||||
|
settings['external-auth-custom-oidc-client-secret'] as string | undefined
|
||||||
|
)
|
||||||
|
return singleton
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the singleton, or raise an exception if it is too soon.
|
||||||
|
* @returns the singleton
|
||||||
|
*/
|
||||||
|
public static singleton (): ExternalAuthOIDC {
|
||||||
|
if (!singleton) {
|
||||||
|
throw new Error('ExternalAuthOIDC singleton is not initialized yet')
|
||||||
|
}
|
||||||
|
return singleton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export {
|
||||||
|
ExternalAuthOIDC
|
||||||
|
}
|
@ -3,6 +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 { loc } from './loc'
|
import { loc } from './loc'
|
||||||
|
|
||||||
type AvatarSet = 'sepia' | 'cat' | 'bird' | 'fenec' | 'abstract' | 'legacy'
|
type AvatarSet = 'sepia' | 'cat' | 'bird' | 'fenec' | 'abstract' | 'legacy'
|
||||||
@ -13,11 +14,14 @@ async function initSettings (options: RegisterServerOptions): Promise<void> {
|
|||||||
initImportantNotesSettings(options)
|
initImportantNotesSettings(options)
|
||||||
initChatSettings(options)
|
initChatSettings(options)
|
||||||
initFederationSettings(options)
|
initFederationSettings(options)
|
||||||
|
initExternalAuth(options)
|
||||||
initAdvancedChannelCustomizationSettings(options)
|
initAdvancedChannelCustomizationSettings(options)
|
||||||
initChatBehaviourSettings(options)
|
initChatBehaviourSettings(options)
|
||||||
initThemingSettings(options)
|
initThemingSettings(options)
|
||||||
initChatServerAdvancedSettings(options)
|
initChatServerAdvancedSettings(options)
|
||||||
|
|
||||||
|
await ExternalAuthOIDC.initSingleton(options)
|
||||||
|
|
||||||
let currentProsodyRoomtype = (await settingsManager.getSettings(['prosody-room-type']))['prosody-room-type']
|
let currentProsodyRoomtype = (await settingsManager.getSettings(['prosody-room-type']))['prosody-room-type']
|
||||||
|
|
||||||
// ********** settings changes management
|
// ********** settings changes management
|
||||||
@ -27,6 +31,8 @@ async function initSettings (options: RegisterServerOptions): Promise<void> {
|
|||||||
await BotsCtl.destroySingleton()
|
await BotsCtl.destroySingleton()
|
||||||
await BotsCtl.initSingleton(options)
|
await BotsCtl.initSingleton(options)
|
||||||
|
|
||||||
|
await ExternalAuthOIDC.initSingleton(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)
|
||||||
|
|
||||||
@ -135,6 +141,77 @@ function initFederationSettings ({ registerSetting }: RegisterServerOptions): vo
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers settings related to the "External Authentication" section.
|
||||||
|
* @param param0 server options
|
||||||
|
*/
|
||||||
|
function initExternalAuth ({ registerSetting }: RegisterServerOptions): void {
|
||||||
|
registerSetting({
|
||||||
|
type: 'html',
|
||||||
|
private: true,
|
||||||
|
descriptionHTML: loc('external_auth_description')
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
name: 'external-auth-custom-oidc',
|
||||||
|
label: loc('external_auth_custom_oidc_label'),
|
||||||
|
descriptionHTML: loc('external_auth_custom_oidc_description'),
|
||||||
|
type: 'input-checkbox',
|
||||||
|
default: false,
|
||||||
|
private: true
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
name: 'external-auth-custom-oidc-button-label',
|
||||||
|
label: loc('external_auth_custom_oidc_button_label_label'),
|
||||||
|
descriptionHTML: loc('external_auth_custom_oidc_button_label_description'),
|
||||||
|
type: 'input',
|
||||||
|
default: '',
|
||||||
|
private: true
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
name: 'external-auth-custom-oidc-discovery-url',
|
||||||
|
label: loc('external_auth_custom_oidc_discovery_url_label'),
|
||||||
|
// descriptionHTML: loc('external_auth_custom_oidc_discovery_url_description'),
|
||||||
|
type: 'input',
|
||||||
|
private: true
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
name: 'external-auth-custom-oidc-client-id',
|
||||||
|
label: loc('external_auth_custom_oidc_client_id_label'),
|
||||||
|
// descriptionHTML: loc('external_auth_custom_oidc_client_id_description'),
|
||||||
|
type: 'input',
|
||||||
|
private: true
|
||||||
|
})
|
||||||
|
registerSetting({
|
||||||
|
name: 'external-auth-custom-oidc-client-secret',
|
||||||
|
label: loc('external_auth_custom_oidc_client_secret_label'),
|
||||||
|
// descriptionHTML: loc('external_auth_custom_oidc_client_secret_description'),
|
||||||
|
type: 'input-password',
|
||||||
|
private: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// registerSetting({
|
||||||
|
// name: 'external-auth-custom-oidc-scope',
|
||||||
|
// label: loc('external_auth_custom_oidc_scope_label'),
|
||||||
|
// descriptionHTML: loc('external_auth_custom_oidc_scope_description'),
|
||||||
|
// type: 'input',
|
||||||
|
// private: true,
|
||||||
|
// default: 'openid profile'
|
||||||
|
// })
|
||||||
|
// registerSetting({
|
||||||
|
// name: 'username-property',
|
||||||
|
// label: 'Username property',
|
||||||
|
// type: 'input',
|
||||||
|
// private: true,
|
||||||
|
// default: 'preferred_username'
|
||||||
|
// })
|
||||||
|
// registerSetting({
|
||||||
|
// name: 'display-name-property',
|
||||||
|
// label: 'Display name property',
|
||||||
|
// type: 'input',
|
||||||
|
// private: true
|
||||||
|
// })
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers settings related to the "Advanced channel customization" section.
|
* Registers settings related to the "Advanced channel customization" section.
|
||||||
* @param param0 server options
|
* @param param0 server options
|
||||||
|
@ -12,6 +12,7 @@ import { loadLoc } from './lib/loc'
|
|||||||
import { RoomChannel } from './lib/room-channel'
|
import { RoomChannel } from './lib/room-channel'
|
||||||
import { BotConfiguration } from './lib/configuration/bot'
|
import { BotConfiguration } from './lib/configuration/bot'
|
||||||
import { BotsCtl } from './lib/bots/ctl'
|
import { BotsCtl } from './lib/bots/ctl'
|
||||||
|
import { ExternalAuthOIDC } from './lib/external-auth/oidc'
|
||||||
import decache from 'decache'
|
import decache from 'decache'
|
||||||
|
|
||||||
// FIXME: Peertube unregister don't have any parameter.
|
// FIXME: Peertube unregister don't have any parameter.
|
||||||
@ -93,6 +94,7 @@ async function unregister (): Promise<any> {
|
|||||||
|
|
||||||
await RoomChannel.destroySingleton()
|
await RoomChannel.destroySingleton()
|
||||||
await BotConfiguration.destroySingleton()
|
await BotConfiguration.destroySingleton()
|
||||||
|
await ExternalAuthOIDC.destroySingleton()
|
||||||
|
|
||||||
const module = __filename
|
const module = __filename
|
||||||
OPTIONS?.peertubeHelpers.logger.info(`Unloading module ${module}...`)
|
OPTIONS?.peertubeHelpers.logger.info(`Unloading module ${module}...`)
|
||||||
|
@ -22,6 +22,9 @@ interface InitConverseJSParams {
|
|||||||
transparent: boolean
|
transparent: boolean
|
||||||
forceDefaultHideMucParticipants?: boolean
|
forceDefaultHideMucParticipants?: boolean
|
||||||
autofocus?: boolean
|
autofocus?: boolean
|
||||||
|
externalAuthOIDC?: {
|
||||||
|
buttonLabel: string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface InitConverseJSParamsError {
|
interface InitConverseJSParamsError {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user