diff --git a/conversejs/builtin.ts b/conversejs/builtin.ts
index a7fd05dd..2edb765b 100644
--- a/conversejs/builtin.ts
+++ b/conversejs/builtin.ts
@@ -1,4 +1,4 @@
-import type { InitConverseJSParams, ChatIncludeMode } from 'shared/lib/types'
+import type { InitConverseJSParams, ChatIncludeMode, OIDCAuthResult } from 'shared/lib/types'
import { inIframe } from './lib/utils'
import { initDom } from './lib/dom'
import {
@@ -28,6 +28,7 @@ declare global {
initConversePlugins: typeof initConversePlugins
initConverse: typeof initConverse
reconnectConverse?: (room: string) => void
+ oidcGetResult?: (data: OIDCAuthResult) => void
}
}
diff --git a/conversejs/custom/livechat-external-login-content.js b/conversejs/custom/livechat-external-login-content.js
index 481dca66..0072ec8c 100644
--- a/conversejs/custom/livechat-external-login-content.js
+++ b/conversejs/custom/livechat-external-login-content.js
@@ -6,6 +6,7 @@ import { __ } from 'i18n'
export default class LivechatExternalLoginContentElement extends CustomElement {
static get properties () {
return {
+ external_auth_oidc_alert_message: { type: String, attribute: false },
remote_peertube_state: { type: String, attribute: false },
remote_peertube_alert_message: { type: String, attribute: false },
remote_peertube_try_anyway_url: { type: String, attribute: false }
@@ -19,13 +20,14 @@ export default class LivechatExternalLoginContentElement extends CustomElement {
render () {
return tplExternalLoginModal(this, {
+ external_auth_oidc_alert_message: this.external_auth_oidc_alert_message,
remote_peertube_state: this.remote_peertube_state,
remote_peertube_alert_message: this.remote_peertube_alert_message,
remote_peertube_try_anyway_url: this.remote_peertube_try_anyway_url
})
}
- onKeyUp (_ev) {
+ onRemotePeertubeKeyUp (_ev) {
if (this.remote_peertube_state !== 'init') {
this.remote_peertube_state = 'init'
this.remote_peertube_alert_message = ''
@@ -109,6 +111,7 @@ export default class LivechatExternalLoginContentElement extends CustomElement {
}
clearAlert () {
+ this.external_auth_oidc_alert_message = ''
this.remote_peertube_alert_message = ''
this.remote_peertube_try_anyway_url = ''
}
diff --git a/conversejs/custom/shared/modals/livechat-external-login.js b/conversejs/custom/shared/modals/livechat-external-login.js
index a583e9b3..7d9d74fb 100644
--- a/conversejs/custom/shared/modals/livechat-external-login.js
+++ b/conversejs/custom/shared/modals/livechat-external-login.js
@@ -15,6 +15,16 @@ class ExternalLoginModal extends BaseModal {
// eslint-disable-next-line no-undef
return __(LOC_login_using_external_account)
}
+
+ onHide () {
+ super.onHide()
+ // kill the oidcGetResult handler if still there
+ try {
+ if (window.oidcGetResult) { window.oidcGetResult() }
+ } catch (err) {
+ console.error(err)
+ }
+ }
}
api.elements.define('converse-livechat-external-login', ExternalLoginModal)
diff --git a/conversejs/custom/templates/livechat-external-login-modal.js b/conversejs/custom/templates/livechat-external-login-modal.js
index a2f935c3..bba2570d 100644
--- a/conversejs/custom/templates/livechat-external-login-modal.js
+++ b/conversejs/custom/templates/livechat-external-login-modal.js
@@ -17,10 +17,48 @@ export const tplExternalLoginModal = (el, o) => {
+ ${!o.external_auth_oidc_alert_message
+ ? ''
+ : html`
${o.external_auth_oidc_alert_message}
`
+ }
`
@@ -33,7 +71,7 @@ export const tplExternalLoginModal = (el, o) => {
placeholder="${i18nRemotePeertubeUrl}"
class="form-control ${o.remote_peertube_alert_message ? 'is-invalid' : ''}"
name="peertube_url"
- @keyup=${el.onKeyUp}
+ @keyup=${el.onRemotePeertubeKeyUp}
?disabled=${o.remote_peertube_state === 'loading'}
/>
diff --git a/conversejs/loc.keys.js b/conversejs/loc.keys.js
index 730fcb2b..ac16a300 100644
--- a/conversejs/loc.keys.js
+++ b/conversejs/loc.keys.js
@@ -13,7 +13,8 @@ const locKeys = [
'login_remote_peertube_no_livechat',
'login_remote_peertube_video_not_found',
'login_remote_peertube_video_not_found_try_anyway',
- 'login_remote_peertube_video_not_found_try_anyway_button'
+ 'login_remote_peertube_video_not_found_try_anyway_button',
+ 'login_external_oidc_alert_message'
]
module.exports = locKeys
diff --git a/languages/en.yml b/languages/en.yml
index a13ef52a..2f3fc312 100644
--- a/languages/en.yml
+++ b/languages/en.yml
@@ -421,3 +421,4 @@ login_remote_peertube_no_livechat: "The livechat plugin is not installed on this
login_remote_peertube_video_not_found: "This video is not available on this Peertube instance."
login_remote_peertube_video_not_found_try_anyway: "In some cases, the video can still be retrieved if you connect to the remote instance."
login_remote_peertube_video_not_found_try_anyway_button: "Try anyway to open the video on the Peertube instance"
+login_external_oidc_alert_message: "Authentication failed"
diff --git a/server/lib/external-auth/oidc.ts b/server/lib/external-auth/oidc.ts
index 54732687..6147661b 100644
--- a/server/lib/external-auth/oidc.ts
+++ b/server/lib/external-auth/oidc.ts
@@ -1,5 +1,5 @@
import type { RegisterServerOptions } from '@peertube/peertube-types'
-import type { Request } from 'express'
+import type { Request, Response, CookieOptions } from 'express'
import { URL } from 'url'
import { Issuer, BaseClient, generators } from 'openid-client'
import { getBaseRouterRoute } from '../helpers'
@@ -37,6 +37,14 @@ class ExternalAuthOIDC {
outputEncoding: 'hex' as Encoding
}
+ private readonly cookieNamePrefix: string = 'peertube-plugin-livechat-oidc-'
+ private readonly cookieOptions: CookieOptions = {
+ secure: true,
+ httpOnly: true,
+ sameSite: 'none',
+ maxAge: 1000 * 60 * 10 // 10 minutes
+ }
+
private ok: boolean | undefined
private issuer: Issuer | undefined | null
@@ -217,12 +225,11 @@ class ExternalAuthOIDC {
/**
* Returns everything that is needed to instanciate an OIDC authentication.
+ * @param req express request
+ * @param res express response. Will add some cookies.
+ * @return the url to which redirect
*/
- async initAuthenticationProcess (): Promise<{
- encryptedCodeVerifier: string
- encryptedState: string
- redirectUrl: string
- }> {
+ async initAuthenticationProcess (req: Request, res: Response): Promise {
if (!this.client) {
throw new Error('External Auth OIDC not loaded yet, too soon to call oidc.initAuthentication')
}
@@ -242,29 +249,27 @@ class ExternalAuthOIDC {
state
})
- return {
- encryptedCodeVerifier,
- encryptedState,
- redirectUrl
- }
+ res.cookie(this.cookieNamePrefix + 'code-verifier', encryptedCodeVerifier, this.cookieOptions)
+ res.cookie(this.cookieNamePrefix + 'state', encryptedState, this.cookieOptions)
+ return redirectUrl
}
/**
* Authentication process callback.
- * @param req The ExpressJS request object.
+ * @param req The ExpressJS request object. Will read cookies.
* @return user info
*/
- async validateAuthenticationProcess (req: Request, cookieNamePrefix: string): Promise {
+ async validateAuthenticationProcess (req: Request): Promise {
if (!this.client) {
throw new Error('External Auth OIDC not loaded yet, too soon to call oidc.validateAuthenticationProcess')
}
- const encryptedCodeVerifier = req.cookies[cookieNamePrefix + 'code-verifier']
+ const encryptedCodeVerifier = req.cookies[this.cookieNamePrefix + 'code-verifier']
if (!encryptedCodeVerifier) {
throw new Error('Received callback but code verifier not found in request cookies.')
}
- const encryptedState = req.cookies[cookieNamePrefix + 'state']
+ const encryptedState = req.cookies[this.cookieNamePrefix + 'state']
if (!encryptedState) {
throw new Error('Received callback but state not found in request cookies.')
}
diff --git a/server/lib/routers/oidc.ts b/server/lib/routers/oidc.ts
index a7acfce2..934939b8 100644
--- a/server/lib/routers/oidc.ts
+++ b/server/lib/routers/oidc.ts
@@ -1,14 +1,32 @@
import type { RegisterServerOptions } from '@peertube/peertube-types'
-import type { Router, Request, Response, NextFunction, CookieOptions } from 'express'
+import type { Router, Request, Response, NextFunction } from 'express'
+import type { OIDCAuthResult } from '../../../shared/lib/types'
import { asyncMiddleware } from '../middlewares/async'
import { ExternalAuthOIDC } from '../external-auth/oidc'
-const cookieNamePrefix = 'peertube-plugin-livechat-oidc-'
-const cookieOptions: CookieOptions = {
- secure: true,
- httpOnly: true,
- sameSite: 'none',
- maxAge: 1000 * 60 * 10 // 10 minutes
+/**
+ * When using a popup for OIDC, writes the HTML/Javascript to close the popup
+ * and send the result to the parent window.
+ * @param result the result to send to the parent window
+ */
+function popupResultHTML (result: OIDCAuthResult): string {
+ return `
+
+
+
+
+ `
}
async function initOIDCRouter (options: RegisterServerOptions): Promise {
@@ -26,10 +44,8 @@ async function initOIDCRouter (options: RegisterServerOptions): Promise
throw new Error('[oidc router] External Auth OIDC not loaded yet')
}
- const authenticationProcess = await oidc.initAuthenticationProcess()
- res.cookie(cookieNamePrefix + 'code-verifier', authenticationProcess.encryptedCodeVerifier, cookieOptions)
- res.cookie(cookieNamePrefix + 'state', authenticationProcess.encryptedState, cookieOptions)
- return res.redirect(authenticationProcess.redirectUrl)
+ const redirectUrl = await oidc.initAuthenticationProcess(req, res)
+ res.redirect(redirectUrl)
} catch (err) {
logger.error('[oidc router] Failed to process the OIDC callback: ' + (err as string))
next()
@@ -38,7 +54,7 @@ async function initOIDCRouter (options: RegisterServerOptions): Promise
))
router.get('/cb', asyncMiddleware(
- async (req: Request, res: Response, next: NextFunction) => {
+ async (req: Request, res: Response, _next: NextFunction) => {
logger.info('[oidc router] OIDC callback call')
try {
const oidc = ExternalAuthOIDC.singleton()
@@ -47,13 +63,20 @@ async function initOIDCRouter (options: RegisterServerOptions): Promise
throw new Error('[oidc router] External Auth OIDC not loaded yet')
}
- const userInfos = await oidc.validateAuthenticationProcess(req, cookieNamePrefix)
- logger.info(JSON.stringify(userInfos)) // FIXME
+ const userInfos = await oidc.validateAuthenticationProcess(req)
+ logger.info(JSON.stringify(userInfos)) // FIXME (normalize data type, process, ...)
- res.send('ok')
+ res.send(popupResultHTML({
+ ok: true,
+ username: userInfos.username,
+ password: 'TODO'
+ }))
} catch (err) {
logger.error('[oidc router] Failed to process the OIDC callback: ' + (err as string))
- next()
+ res.sendStatus(500)
+ res.send(popupResultHTML({
+ ok: false
+ }))
}
}
))
diff --git a/shared/lib/types.ts b/shared/lib/types.ts
index 6a109dbb..24dfcb4a 100644
--- a/shared/lib/types.ts
+++ b/shared/lib/types.ts
@@ -107,6 +107,19 @@ type ChatPeertubeIncludeMode = 'peertube-fullpage' | 'peertube-video'
*/
type ChatIncludeMode = 'chat-only' | ChatPeertubeIncludeMode
+interface OIDCAuthResultError {
+ ok: true
+ username: string
+ password: string
+}
+
+interface OIDCAuthResultOk {
+ ok: false
+ message?: string
+}
+
+type OIDCAuthResult = OIDCAuthResultError | OIDCAuthResultOk
+
export type {
ConverseJSTheme,
InitConverseJSParams,
@@ -117,5 +130,8 @@ export type {
ChannelConfigurationOptions,
ChannelConfiguration,
ChatIncludeMode,
- ChatPeertubeIncludeMode
+ ChatPeertubeIncludeMode,
+ OIDCAuthResultError,
+ OIDCAuthResultOk,
+ OIDCAuthResult
}