peertube-theme-nctv-dark/peertube-plugin-auth-saml2/main.js

280 lines
6.9 KiB
JavaScript
Raw Normal View History

2020-05-04 09:33:05 +00:00
const saml2 = require('saml2-js')
const crypto = require('crypto')
const store = {
assertUrl: null,
authDisplayName: 'SAML 2',
serviceProvider: null,
identityProvider: null
}
async function register ({
registerExternalAuth,
unregisterExternalAuth,
registerSetting,
settingsManager,
storageManager,
peertubeHelpers,
getRouter
}) {
const { logger } = peertubeHelpers
const metadataUrl = peertubeHelpers.config.getWebserverUrl() + '/plugins/auth-saml2/router/metadata.xml'
registerSetting({
name: 'client-id',
label: 'Client ID',
type: 'input',
private: true,
default: metadataUrl
})
registerSetting({
name: 'auth-display-name',
label: 'Auth display name',
type: 'input',
private: true,
default: 'SAML 2'
})
registerSetting({
name: 'login-url',
label: 'SSO login URL',
type: 'input',
private: true
})
registerSetting({
name: 'provider-certificate',
2020-05-04 13:34:58 +00:00
label: 'Identity provider certificate (PEM format)',
type: 'input-textarea',
private: true
})
registerSetting({
name: 'service-certificate',
label: 'Service certificate (PEM format)',
type: 'input-textarea',
private: true
})
registerSetting({
name: 'service-private-key',
label: 'Service private key (PEM format)',
2020-05-04 09:33:05 +00:00
type: 'input-textarea',
private: true
})
2020-05-04 13:34:58 +00:00
registerSetting({
name: 'sign-get-request',
label: 'Sign get request',
type: 'input-checkbox',
private: true,
default: false
})
2020-05-04 09:33:05 +00:00
registerSetting({
name: 'username-property',
label: 'Username property',
type: 'input',
private: true,
default: 'preferred_username'
})
registerSetting({
name: 'mail-property',
label: 'Email property',
type: 'input',
private: true,
default: 'email'
})
registerSetting({
name: 'display-name-property',
label: 'Display name property',
type: 'input',
private: true
})
registerSetting({
name: 'role-property',
label: 'Role property',
type: 'input',
private: true
})
const router = getRouter()
store.assertUrl = peertubeHelpers.config.getWebserverUrl() + '/plugins/auth-saml2/router/assert'
router.post('/assert', (req, res) => handleAssert(peertubeHelpers, settingsManager, req, res))
router.get('/metadata.xml', (req, res) => {
if (!store.serviceProvider) {
logger.warn('Cannot get SAML 2 metadata: service provider not created.')
return res.sendStatus(400)
}
res.type('application/xml').send(store.serviceProvider.create_metadata())
})
await loadSettingsAndCreateProviders(registerExternalAuth, unregisterExternalAuth, peertubeHelpers, settingsManager, storageManager)
store.authDisplayName = await settingsManager.getSetting('auth-display-name')
settingsManager.onSettingsChange(settings => {
loadSettingsAndCreateProviders(registerExternalAuth, unregisterExternalAuth, peertubeHelpers, settingsManager, storageManager)
.catch(err => logger.error('Cannot load settings and create client after settings changes.', { err }))
if (settings['auth-display-name']) store.authDisplayName = settings['auth-display-name']
})
}
async function unregister () {
return
}
module.exports = {
register,
unregister
}
// ############################################################################
async function loadSettingsAndCreateProviders (
registerExternalAuth,
unregisterExternalAuth,
peertubeHelpers,
settingsManager,
storageManager
) {
const { logger } = peertubeHelpers
if (store.serviceProvider || store.identityProvider) {
unregisterExternalAuth('saml2')
}
store.serviceProvider = null
store.identityProvider = null
const settings = await settingsManager.getSettings([
'client-id',
'sign-get-request',
'login-url',
2020-05-04 13:34:58 +00:00
'provider-certificate',
'service-certificate',
'service-private-key'
2020-05-04 09:33:05 +00:00
])
if (!settings['login-url']) {
logger.info('Do not register external saml2 auth because login URL is not set.')
return
}
if (!settings['provider-certificate']) {
logger.info('Do not register external saml2 auth because provider certificate is not set.')
return
}
2020-05-04 13:34:58 +00:00
logger.debug('Creating SAML service/identity instances.', { settings })
2020-05-04 09:33:05 +00:00
const serviceOptions = {
entity_id: settings['client-id'],
2020-05-04 13:34:58 +00:00
private_key: settings['service-private-key'],
certificate: settings['service-certificate'],
2020-05-04 09:33:05 +00:00
assert_endpoint: store.assertUrl
}
store.serviceProvider = new saml2.ServiceProvider(serviceOptions)
const identityOptions = {
sso_login_url: settings['login-url'],
certificates: [
settings['provider-certificate']
],
sign_get_request: settings['sign-get-request'],
allow_unencrypted_assertion: true
}
store.identityProvider = new saml2.IdentityProvider(identityOptions)
const result = registerExternalAuth({
authName: 'saml2',
authDisplayName: () => store.authDisplayName,
onAuthRequest: async (req, res) => {
2020-05-04 13:34:58 +00:00
try {
store.serviceProvider.create_login_request_url(store.identityProvider, {}, (err, loginUrl, requestId) => {
if (err) {
logger.error('Cannot SAML 2 authenticate.', { err })
return redirectOnError(res)
}
res.redirect(loginUrl)
})
} catch (err) {
logger.error('Cannot create login request url.', { err })
return redirectOnError(res)
}
2020-05-04 09:33:05 +00:00
}
})
store.userAuthenticated = result.userAuthenticated
}
function handleAssert(peertubeHelpers, settingsManager, req, res) {
const { logger } = peertubeHelpers
const options = { request_body: req.body }
store.serviceProvider.post_assert(store.identityProvider, options, async (err, samlResponse) => {
if (err) {
logger.error('Error SAML 2 assert.', { err })
return redirectOnError(res)
}
logger.debug('User authenticated by SAML 2.', { samlResponse })
try {
const user = await buildUser(settingsManager, samlResponse.user)
return store.userAuthenticated({
req,
res,
...user
})
} catch (err) {
logger.error('Error SAML 2 build user.', { err })
return redirectOnError(res)
}
});
}
function redirectOnError (res) {
res.redirect('/login?externalAuthError=true')
}
function findInUser (samlUser, key) {
if (!key) return undefined
if (samlUser[key]) return samlUser[key]
if (samlUser.attributes[key]) return samlUser.attributes[key][0]
return undefined
}
async function buildUser (settingsManager, samlUser) {
const settings = await settingsManager.getSettings([
'mail-property',
'username-property',
'display-name-property',
'role-property'
])
let username = findInUser(samlUser, settings['username-property']) || ''
username = username.replace(/[^a-z0-9._]/g, '_')
return {
username,
email: findInUser(samlUser, settings['mail-property']),
displayName: findInUser(samlUser, settings['display-name-property']),
role: findInUser(samlUser, settings['role-property'])
}
}