peertube-plugin-livechat/server/lib/external-auth/oidc.ts

191 lines
5.4 KiB
TypeScript

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
private issuer: Issuer | undefined | null
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
}
/**
* Get the button
* @returns Button label
*/
getButtonLabel (): string | undefined {
return this.buttonLabel
}
/**
* Get the discovery URL
* @returns discoveryURL
*/
getDiscoveryUrl (): string | undefined {
return this.discoveryUrl
}
/**
* 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 ?? '') === '') {
errors.push('Missing button label')
}
if ((this.discoveryUrl ?? '') === '') {
errors.push('Missing discovery url')
} else {
try {
const uri = new URL(this.discoveryUrl ?? 'wrong url')
this.logger.debug('OIDC Discovery url is valid: ' + uri.toString())
} catch (err) {
errors.push('Invalid discovery url')
}
}
if ((this.clientId ?? '') === '') {
errors.push('Missing client id')
}
if ((this.clientSecret ?? '') === '') {
errors.push('Missing client secret')
}
if (errors.length) {
this.logger.error('OIDC is not ok: ' + JSON.stringify(errors))
}
return errors
}
/**
* Ensure the issuer is loaded.
* @returns the issuer if enabled
*/
async loadIssuer (): Promise<Issuer | null> {
// this.issuer === null means we already tried, but it failed.
if (this.issuer !== undefined) { return this.issuer }
if (!await this.isOk()) { return null }
try {
this.issuer = await Issuer.discover(this.discoveryUrl as string)
this.logger.debug(`Discovered issuer, metadata are: ${JSON.stringify(this.issuer.metadata)}`)
} catch (err) {
this.logger.error(err as string)
this.issuer = null
}
return this.issuer
}
/**
* 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
}